Commit ba31e3ab by tatsukiishikawa

init first labs

parent ef8e8a37
Showing with 26421 additions and 0 deletions
build
!.vscode/*
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.2.0)
set(toolchainVersion 14_2_Rel1)
set(picotoolVersion 2.2.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD pico_w CACHE STRING "Board type")
cmake_minimum_required(VERSION 3.12)
include(pico_sdk_import.cmake)
project(pico-10dof-imu_project)
pico_sdk_init()
add_subdirectory(icm20948)
include_directories(./icm20948)
add_executable(imu
main.c
)
pico_enable_stdio_usb(imu 1)
pico_enable_stdio_uart(imu 1)
pico_add_extra_outputs(imu)
# Pull in our pico_stdlib which pulls in commonly used features
target_link_libraries(imu pico_stdlib icm20948 hardware_i2c pico_multicore)
aux_source_directory(. DIR_icm20948_SRCS)
add_library(icm20948 ${DIR_icm20948_SRCS})
target_link_libraries(icm20948 PUBLIC hardware_i2c pico_stdlib)
#include "icm20948.h"
#include <string.h>
#define I2C_PORT i2c1
IMU_ST_SENSOR_DATA gstGyroOffset ={0,0,0};
#ifdef __cplusplus
extern "C" {
#endif
void imuAHRSupdate(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz);
float invSqrt(float x);
void icm20948init(void);
bool icm20948Check(void);
void icm20948GyroRead(int16_t* ps16X, int16_t* ps16Y, int16_t* ps16Z);
void icm20948AccelRead(int16_t* ps16X, int16_t* ps16Y, int16_t* ps16Z);
void icm20948GyroFastRead(int16_t* ps16X, int16_t* ps16Y, int16_t* ps16Z);
void icm20948AccelFastRead(int16_t* ps16X, int16_t* ps16Y, int16_t* ps16Z);
void icm20948MagRead(int16_t* ps16X, int16_t* ps16Y, int16_t* ps16Z);
bool icm20948MagCheck(void);
void icm20948CalAvgValue(uint8_t *pIndex, int16_t *pAvgBuffer, int16_t InVal, int32_t *pOutVal);
void icm20948GyroOffset(void);
void icm20948ReadSecondary(uint8_t u8I2CAddr, uint8_t u8RegAddr, uint8_t u8Len, uint8_t *pu8data);
void icm20948WriteSecondary(uint8_t u8I2CAddr, uint8_t u8RegAddr, uint8_t u8data);
bool icm20948Check(void);
char I2C_ReadOneByte(char reg)
{
char buf;
i2c_write_blocking(I2C_PORT,I2C_ADD_ICM20948,&reg,1,true);
i2c_read_blocking(I2C_PORT,I2C_ADD_ICM20948,&buf,1,false);
return buf;
}
void I2C_WriteOneByte( char reg, char value)
{
char buf[]={reg,value};
i2c_write_blocking(I2C_PORT,I2C_ADD_ICM20948,buf,2,false);
}
/******************************************************************************
* IMU module *
******************************************************************************/
#define Kp 4.50f // proportional gain governs rate of convergence to accelerometer/magnetometer
#define Ki 1.0f // integral gain governs rate of convergence of gyroscope biases
float angles[3];
float q0, q1, q2, q3;
void imuInit(IMU_EN_SENSOR_TYPE *penMotionSensorType)
{
bool bRet = false;
i2c_init(I2C_PORT,400*1000);
gpio_set_function(6, GPIO_FUNC_I2C);
gpio_set_function(7, GPIO_FUNC_I2C);
gpio_pull_up(6);
gpio_pull_up(7);
bRet = icm20948Check();
if( true == bRet)
{
*penMotionSensorType = IMU_EN_SENSOR_TYPE_ICM20948;
icm20948init();
}
else
{
*penMotionSensorType = IMU_EN_SENSOR_TYPE_NULL;
}
q0 = 1.0f;
q1 = 0.0f;
q2 = 0.0f;
q3 = 0.0f;
return;
}
void imuDataAccGyrGet(IMU_ST_SENSOR_DATA *pstGyroRawData,
IMU_ST_SENSOR_DATA *pstAccelRawData)
{
int16_t s16Gyro[3], s16Accel[3], s16Magn[3];
icm20948AccelFastRead(&s16Accel[0], &s16Accel[1], &s16Accel[2]);
icm20948GyroFastRead(&s16Gyro[0], &s16Gyro[1], &s16Gyro[2]);
// icm20948AccelRead(&s16Accel[0], &s16Accel[1], &s16Accel[2]);
// icm20948GyroRead(&s16Gyro[0], &s16Gyro[1], &s16Gyro[2]);
pstGyroRawData->s16X = s16Gyro[0];
pstGyroRawData->s16Y = s16Gyro[1];
pstGyroRawData->s16Z = s16Gyro[2];
pstAccelRawData->s16X = s16Accel[0];
pstAccelRawData->s16Y = s16Accel[1];
pstAccelRawData->s16Z = s16Accel[2];
return;
}
void imuDataGet(IMU_ST_ANGLES_DATA *pstAngles,
IMU_ST_SENSOR_DATA *pstGyroRawData,
IMU_ST_SENSOR_DATA *pstAccelRawData,
IMU_ST_SENSOR_DATA *pstMagnRawData)
{
float MotionVal[9];
int16_t s16Gyro[3], s16Accel[3], s16Magn[3];
icm20948AccelRead(&s16Accel[0], &s16Accel[1], &s16Accel[2]);
icm20948GyroRead(&s16Gyro[0], &s16Gyro[1], &s16Gyro[2]);
icm20948MagRead(&s16Magn[0], &s16Magn[1], &s16Magn[2]);
MotionVal[0]=s16Gyro[0]/32.8;
MotionVal[1]=s16Gyro[1]/32.8;
MotionVal[2]=s16Gyro[2]/32.8;
MotionVal[3]=s16Accel[0];
MotionVal[4]=s16Accel[1];
MotionVal[5]=s16Accel[2];
MotionVal[6]=s16Magn[0];
MotionVal[7]=s16Magn[1];
MotionVal[8]=s16Magn[2];
imuAHRSupdate((float)MotionVal[0] * 0.0175, (float)MotionVal[1] * 0.0175, (float)MotionVal[2] * 0.0175,
(float)MotionVal[3], (float)MotionVal[4], (float)MotionVal[5],
(float)MotionVal[6], (float)MotionVal[7], MotionVal[8]);
pstAngles->fPitch = asin(-2 * q1 * q3 + 2 * q0* q2)* 57.3; // pitch
pstAngles->fRoll = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2* q2 + 1)* 57.3; // roll
pstAngles->fYaw = atan2(-2 * q1 * q2 - 2 * q0 * q3, 2 * q2 * q2 + 2 * q3 * q3 - 1) * 57.3;
pstGyroRawData->s16X = s16Gyro[0];
pstGyroRawData->s16Y = s16Gyro[1];
pstGyroRawData->s16Z = s16Gyro[2];
pstAccelRawData->s16X = s16Accel[0];
pstAccelRawData->s16Y = s16Accel[1];
pstAccelRawData->s16Z = s16Accel[2];
pstMagnRawData->s16X = s16Magn[0];
pstMagnRawData->s16Y = s16Magn[1];
pstMagnRawData->s16Z = s16Magn[2];
return;
}
void imuAHRSupdate(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz)
{
float norm;
float hx, hy, hz, bx, bz;
float vx, vy, vz, wx, wy, wz;
float exInt = 0.0, eyInt = 0.0, ezInt = 0.0;
float ex, ey, ez, halfT = 0.024f;
float q0q0 = q0 * q0;
float q0q1 = q0 * q1;
float q0q2 = q0 * q2;
float q0q3 = q0 * q3;
float q1q1 = q1 * q1;
float q1q2 = q1 * q2;
float q1q3 = q1 * q3;
float q2q2 = q2 * q2;
float q2q3 = q2 * q3;
float q3q3 = q3 * q3;
norm = invSqrt(ax * ax + ay * ay + az * az);
ax = ax * norm;
ay = ay * norm;
az = az * norm;
norm = invSqrt(mx * mx + my * my + mz * mz);
mx = mx * norm;
my = my * norm;
mz = mz * norm;
// compute reference direction of flux
hx = 2 * mx * (0.5f - q2q2 - q3q3) + 2 * my * (q1q2 - q0q3) + 2 * mz * (q1q3 + q0q2);
hy = 2 * mx * (q1q2 + q0q3) + 2 * my * (0.5f - q1q1 - q3q3) + 2 * mz * (q2q3 - q0q1);
hz = 2 * mx * (q1q3 - q0q2) + 2 * my * (q2q3 + q0q1) + 2 * mz * (0.5f - q1q1 - q2q2);
bx = sqrt((hx * hx) + (hy * hy));
bz = hz;
// estimated direction of gravity and flux (v and w)
vx = 2 * (q1q3 - q0q2);
vy = 2 * (q0q1 + q2q3);
vz = q0q0 - q1q1 - q2q2 + q3q3;
wx = 2 * bx * (0.5 - q2q2 - q3q3) + 2 * bz * (q1q3 - q0q2);
wy = 2 * bx * (q1q2 - q0q3) + 2 * bz * (q0q1 + q2q3);
wz = 2 * bx * (q0q2 + q1q3) + 2 * bz * (0.5 - q1q1 - q2q2);
// error is sum of cross product between reference direction of fields and direction measured by sensors
ex = (ay * vz - az * vy) + (my * wz - mz * wy);
ey = (az * vx - ax * vz) + (mz * wx - mx * wz);
ez = (ax * vy - ay * vx) + (mx * wy - my * wx);
if(ex != 0.0f && ey != 0.0f && ez != 0.0f)
{
exInt = exInt + ex * Ki * halfT;
eyInt = eyInt + ey * Ki * halfT;
ezInt = ezInt + ez * Ki * halfT;
gx = gx + Kp * ex + exInt;
gy = gy + Kp * ey + eyInt;
gz = gz + Kp * ez + ezInt;
}
q0 = q0 + (-q1 * gx - q2 * gy - q3 * gz) * halfT;
q1 = q1 + (q0 * gx + q2 * gz - q3 * gy) * halfT;
q2 = q2 + (q0 * gy - q1 * gz + q3 * gx) * halfT;
q3 = q3 + (q0 * gz + q1 * gy - q2 * gx) * halfT;
norm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
q0 = q0 * norm;
q1 = q1 * norm;
q2 = q2 * norm;
q3 = q3 * norm;
}
float invSqrt(float x)
{
float halfx = 0.5f * x;
float y = x;
long i = *(long*)&y; //get bits for floating value
i = 0x5f3759df - (i >> 1); //gives initial guss you
y = *(float*)&i; //convert bits back to float
y = y * (1.5f - (halfx * y * y)); //newtop step, repeating increases accuracy
return y;
}
/******************************************************************************
* icm20948 sensor device *
******************************************************************************/
void icm20948init(void)
{
/* user bank 0 register */
I2C_WriteOneByte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_0);
I2C_WriteOneByte( REG_ADD_PWR_MIGMT_1, REG_VAL_ALL_RGE_RESET);
sleep_ms(10);
I2C_WriteOneByte( REG_ADD_PWR_MIGMT_1, REG_VAL_RUN_MODE);
/* user bank 2 register */
I2C_WriteOneByte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_2);
I2C_WriteOneByte( REG_ADD_GYRO_SMPLRT_DIV, 0x07);
I2C_WriteOneByte( REG_ADD_GYRO_CONFIG_1,
REG_VAL_BIT_GYRO_DLPCFG_6 | REG_VAL_BIT_GYRO_FS_1000DPS | REG_VAL_BIT_GYRO_DLPF);
I2C_WriteOneByte( REG_ADD_ACCEL_SMPLRT_DIV_2, 0x07);
I2C_WriteOneByte( REG_ADD_ACCEL_CONFIG,
REG_VAL_BIT_ACCEL_DLPCFG_6 | REG_VAL_BIT_ACCEL_FS_2g | REG_VAL_BIT_ACCEL_DLPF);
/* user bank 0 register */
I2C_WriteOneByte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_0);
sleep_ms(100);
/* offset */
icm20948GyroOffset();
icm20948MagCheck();
icm20948WriteSecondary( I2C_ADD_ICM20948_AK09916|I2C_ADD_ICM20948_AK09916_WRITE,
REG_ADD_MAG_CNTL2, REG_VAL_MAG_MODE_20HZ);
return;
}
bool icm20948Check(void)
{
bool bRet = false;
if(REG_VAL_WIA == I2C_ReadOneByte( REG_ADD_WIA))
{
bRet = true;
}
return bRet;
}
void icm20948GyroRead(int16_t* ps16X, int16_t* ps16Y, int16_t* ps16Z)
{
uint8_t u8Buf[6];
int16_t s16Buf[3] = {0};
uint8_t i;
int32_t s32OutBuf[3] = {0};
static ICM20948_ST_AVG_DATA sstAvgBuf[3];
static int16_t ss16c = 0;
ss16c++;
u8Buf[0]=I2C_ReadOneByte(REG_ADD_GYRO_XOUT_L);
u8Buf[1]=I2C_ReadOneByte(REG_ADD_GYRO_XOUT_H);
s16Buf[0]= (u8Buf[1]<<8)|u8Buf[0];
u8Buf[0]=I2C_ReadOneByte(REG_ADD_GYRO_YOUT_L);
u8Buf[1]=I2C_ReadOneByte(REG_ADD_GYRO_YOUT_H);
s16Buf[1]= (u8Buf[1]<<8)|u8Buf[0];
u8Buf[0]=I2C_ReadOneByte(REG_ADD_GYRO_ZOUT_L);
u8Buf[1]=I2C_ReadOneByte(REG_ADD_GYRO_ZOUT_H);
s16Buf[2]= (u8Buf[1]<<8)|u8Buf[0];
for(i = 0; i < 3; i ++)
{
icm20948CalAvgValue(&sstAvgBuf[i].u8Index, sstAvgBuf[i].s16AvgBuffer, s16Buf[i], s32OutBuf + i);
}
*ps16X = s32OutBuf[0] - gstGyroOffset.s16X;
*ps16Y = s32OutBuf[1] - gstGyroOffset.s16Y;
*ps16Z = s32OutBuf[2] - gstGyroOffset.s16Z;
return;
}
void icm20948AccelRead(int16_t* ps16X, int16_t* ps16Y, int16_t* ps16Z)
{
uint8_t u8Buf[2];
int16_t s16Buf[3] = {0};
uint8_t i;
int32_t s32OutBuf[3] = {0};
static ICM20948_ST_AVG_DATA sstAvgBuf[3];
u8Buf[0]=I2C_ReadOneByte(REG_ADD_ACCEL_XOUT_L);
u8Buf[1]=I2C_ReadOneByte(REG_ADD_ACCEL_XOUT_H);
s16Buf[0]= (u8Buf[1]<<8)|u8Buf[0];
u8Buf[0]=I2C_ReadOneByte(REG_ADD_ACCEL_YOUT_L);
u8Buf[1]=I2C_ReadOneByte(REG_ADD_ACCEL_YOUT_H);
s16Buf[1]= (u8Buf[1]<<8)|u8Buf[0];
u8Buf[0]=I2C_ReadOneByte(REG_ADD_ACCEL_ZOUT_L);
u8Buf[1]=I2C_ReadOneByte(REG_ADD_ACCEL_ZOUT_H);
s16Buf[2]= (u8Buf[1]<<8)|u8Buf[0];
for(i = 0; i < 3; i ++)
{
icm20948CalAvgValue(&sstAvgBuf[i].u8Index, sstAvgBuf[i].s16AvgBuffer, s16Buf[i], s32OutBuf + i);
}
*ps16X = s32OutBuf[0];
*ps16Y = s32OutBuf[1];
*ps16Z = s32OutBuf[2];
return;
}
// ---- Fast burst read over I2C (one transaction) ----
static inline void icm20948ReadBurst(uint8_t start_reg, uint8_t *buf, size_t n) {
i2c_write_blocking(I2C_PORT, I2C_ADD_ICM20948, &start_reg, 1, true); // repeated START
i2c_read_blocking(I2C_PORT, I2C_ADD_ICM20948, buf, n, false); // STOP
}
// ---- O(1) 8-sample moving average state ----
typedef struct {
uint8_t idx;
int16_t buf[8];
int32_t sum;
bool primed;
} Avg8;
static inline int16_t avg8_update(Avg8 *s, int16_t v) {
if (!s->primed) {
// Prime: fill with first value so the initial average is correct immediately
for (int i = 0; i < 8; ++i) s->buf[i] = v;
s->sum = (int32_t)v * 8;
s->idx = 0;
s->primed = true;
return v;
}
s->sum -= s->buf[s->idx];
s->buf[s->idx] = v;
s->sum += v;
s->idx = (s->idx + 1) & 7; // modulo 8
return (int16_t)(s->sum >> 3);
}
void icm20948AccelFastRead(int16_t* ps16X, int16_t* ps16Y, int16_t* ps16Z)
{
static Avg8 s_acc_avg[3] = {0};
uint8_t b[6];
icm20948ReadBurst(REG_ADD_ACCEL_XOUT_H, b, 6);
int16_t rawX = (int16_t)((b[0] << 8) | b[1]);
int16_t rawY = (int16_t)((b[2] << 8) | b[3]);
int16_t rawZ = (int16_t)((b[4] << 8) | b[5]);
// O(1) smoothing
*ps16X = avg8_update(&s_acc_avg[0], rawX);
*ps16Y = avg8_update(&s_acc_avg[1], rawY);
*ps16Z = avg8_update(&s_acc_avg[2], rawZ);
return;
}
void icm20948GyroFastRead(int16_t* ps16X, int16_t* ps16Y, int16_t* ps16Z)
{
static Avg8 s_gyro_avg[3] = {0};
uint8_t b[6];
icm20948ReadBurst(REG_ADD_GYRO_XOUT_H, b, 6);
int16_t rawX = (int16_t)((b[0] << 8) | b[1]);
int16_t rawY = (int16_t)((b[2] << 8) | b[3]);
int16_t rawZ = (int16_t)((b[4] << 8) | b[5]);
int16_t avgX = avg8_update(&s_gyro_avg[0], rawX);
int16_t avgY = avg8_update(&s_gyro_avg[1], rawY);
int16_t avgZ = avg8_update(&s_gyro_avg[2], rawZ);
// Keep your boot-time bias subtraction
*ps16X = (int16_t)((int32_t)avgX - gstGyroOffset.s16X);
*ps16Y = (int16_t)((int32_t)avgY - gstGyroOffset.s16Y);
*ps16Z = (int16_t)((int32_t)avgZ - gstGyroOffset.s16Z);
return;
}
void icm20948MagRead(int16_t* ps16X, int16_t* ps16Y, int16_t* ps16Z)
{
uint8_t counter = 20;
uint8_t u8Data[MAG_DATA_LEN];
int16_t s16Buf[3] = {0};
uint8_t i;
int32_t s32OutBuf[3] = {0};
static ICM20948_ST_AVG_DATA sstAvgBuf[3];
while( counter>0 )
{
sleep_ms(10);
icm20948ReadSecondary( I2C_ADD_ICM20948_AK09916|I2C_ADD_ICM20948_AK09916_READ,
REG_ADD_MAG_ST2, 1, u8Data);
if ((u8Data[0] & 0x01) != 0)
break;
counter--;
}
if(counter != 0)
{
icm20948ReadSecondary( I2C_ADD_ICM20948_AK09916|I2C_ADD_ICM20948_AK09916_READ,
REG_ADD_MAG_DATA,
MAG_DATA_LEN,
u8Data);
s16Buf[0] = ((int16_t)u8Data[1]<<8) | u8Data[0];
s16Buf[1] = ((int16_t)u8Data[3]<<8) | u8Data[2];
s16Buf[2] = ((int16_t)u8Data[5]<<8) | u8Data[4];
}
for(i = 0; i < 3; i ++)
{
icm20948CalAvgValue(&sstAvgBuf[i].u8Index, sstAvgBuf[i].s16AvgBuffer, s16Buf[i], s32OutBuf + i);
}
*ps16X = s32OutBuf[0];
*ps16Y = -s32OutBuf[1];
*ps16Z = -s32OutBuf[2];
return;
}
void icm20948ReadSecondary(uint8_t u8I2CAddr, uint8_t u8RegAddr, uint8_t u8Len, uint8_t *pu8data)
{
uint8_t i;
uint8_t u8Temp;
I2C_WriteOneByte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_3); //swtich bank3
I2C_WriteOneByte( REG_ADD_I2C_SLV0_ADDR, u8I2CAddr);
I2C_WriteOneByte( REG_ADD_I2C_SLV0_REG, u8RegAddr);
I2C_WriteOneByte( REG_ADD_I2C_SLV0_CTRL, REG_VAL_BIT_SLV0_EN|u8Len);
I2C_WriteOneByte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_0); //swtich bank0
u8Temp = I2C_ReadOneByte(REG_ADD_USER_CTRL);
u8Temp |= REG_VAL_BIT_I2C_MST_EN;
I2C_WriteOneByte( REG_ADD_USER_CTRL, u8Temp);
sleep_ms(5);
u8Temp &= ~REG_VAL_BIT_I2C_MST_EN;
I2C_WriteOneByte( REG_ADD_USER_CTRL, u8Temp);
for(i=0; i<u8Len; i++)
{
*(pu8data+i) = I2C_ReadOneByte( REG_ADD_EXT_SENS_DATA_00+i);
}
I2C_WriteOneByte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_3); //swtich bank3
u8Temp = I2C_ReadOneByte(REG_ADD_I2C_SLV0_CTRL);
u8Temp &= ~((REG_VAL_BIT_I2C_MST_EN)&(REG_VAL_BIT_MASK_LEN));
I2C_WriteOneByte( REG_ADD_I2C_SLV0_CTRL, u8Temp);
I2C_WriteOneByte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_0); //swtich bank0
}
void icm20948WriteSecondary(uint8_t u8I2CAddr, uint8_t u8RegAddr, uint8_t u8data)
{
uint8_t u8Temp;
I2C_WriteOneByte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_3); //swtich bank3
I2C_WriteOneByte( REG_ADD_I2C_SLV1_ADDR, u8I2CAddr);
I2C_WriteOneByte( REG_ADD_I2C_SLV1_REG, u8RegAddr);
I2C_WriteOneByte( REG_ADD_I2C_SLV1_DO, u8data);
I2C_WriteOneByte( REG_ADD_I2C_SLV1_CTRL, REG_VAL_BIT_SLV0_EN|1);
I2C_WriteOneByte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_0); //swtich bank0
u8Temp = I2C_ReadOneByte(REG_ADD_USER_CTRL);
u8Temp |= REG_VAL_BIT_I2C_MST_EN;
I2C_WriteOneByte( REG_ADD_USER_CTRL, u8Temp);
sleep_ms(5);
u8Temp &= ~REG_VAL_BIT_I2C_MST_EN;
I2C_WriteOneByte( REG_ADD_USER_CTRL, u8Temp);
I2C_WriteOneByte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_3); //swtich bank3
u8Temp = I2C_ReadOneByte(REG_ADD_I2C_SLV0_CTRL);
u8Temp &= ~((REG_VAL_BIT_I2C_MST_EN)&(REG_VAL_BIT_MASK_LEN));
I2C_WriteOneByte( REG_ADD_I2C_SLV0_CTRL, u8Temp);
I2C_WriteOneByte( REG_ADD_REG_BANK_SEL, REG_VAL_REG_BANK_0); //swtich bank0
return;
}
void icm20948CalAvgValue(uint8_t *pIndex, int16_t *pAvgBuffer, int16_t InVal, int32_t *pOutVal)
{
uint8_t i;
*(pAvgBuffer + ((*pIndex) ++)) = InVal;
*pIndex &= 0x07;
*pOutVal = 0;
for(i = 0; i < 8; i ++)
{
*pOutVal += *(pAvgBuffer + i);
}
*pOutVal >>= 3;
}
void icm20948GyroOffset(void)
{
uint8_t i;
int16_t s16Gx = 0, s16Gy = 0, s16Gz = 0;
int32_t s32TempGx = 0, s32TempGy = 0, s32TempGz = 0;
for(i = 0; i < 32; i ++)
{
icm20948GyroRead(&s16Gx, &s16Gy, &s16Gz);
s32TempGx += s16Gx;
s32TempGy += s16Gy;
s32TempGz += s16Gz;
sleep_ms(10);
}
gstGyroOffset.s16X = s32TempGx >> 5;
gstGyroOffset.s16Y = s32TempGy >> 5;
gstGyroOffset.s16Z = s32TempGz >> 5;
return;
}
bool icm20948MagCheck(void)
{
bool bRet = false;
uint8_t u8Ret[2];
icm20948ReadSecondary( I2C_ADD_ICM20948_AK09916|I2C_ADD_ICM20948_AK09916_READ,
REG_ADD_MAG_WIA1, 2,u8Ret);
if( (u8Ret[0] == REG_VAL_MAG_WIA1) && ( u8Ret[1] == REG_VAL_MAG_WIA2) )
{
bRet = true;
}
return bRet;
}
#ifdef __cplusplus
}
#endif
#ifndef __ICM20948__H
#define __ICM20948__H
#include <stdio.h>
#include <math.h>
#include "hardware/i2c.h"
#include "pico/stdlib.h"
//typedef uint8_t bool;
#define true 1
#define false 0
/* define ICM-20948 Device I2C address*/
#define I2C_ADD_ICM20948 0x68
#define I2C_ADD_ICM20948_AK09916 0x0C
#define I2C_ADD_ICM20948_AK09916_READ 0x80
#define I2C_ADD_ICM20948_AK09916_WRITE 0x00
/* define ICM-20948 Register */
/* user bank 0 register */
#define REG_ADD_WIA 0x00
#define REG_VAL_WIA 0xEA
#define REG_ADD_USER_CTRL 0x03
#define REG_VAL_BIT_DMP_EN 0x80
#define REG_VAL_BIT_FIFO_EN 0x40
#define REG_VAL_BIT_I2C_MST_EN 0x20
#define REG_VAL_BIT_I2C_IF_DIS 0x10
#define REG_VAL_BIT_DMP_RST 0x08
#define REG_VAL_BIT_DIAMOND_DMP_RST 0x04
#define REG_ADD_PWR_MIGMT_1 0x06
#define REG_VAL_ALL_RGE_RESET 0x80
#define REG_VAL_RUN_MODE 0x01 //Non low-power mode
#define REG_ADD_LP_CONFIG 0x05
#define REG_ADD_PWR_MGMT_1 0x06
#define REG_ADD_PWR_MGMT_2 0x07
#define REG_ADD_ACCEL_XOUT_H 0x2D
#define REG_ADD_ACCEL_XOUT_L 0x2E
#define REG_ADD_ACCEL_YOUT_H 0x2F
#define REG_ADD_ACCEL_YOUT_L 0x30
#define REG_ADD_ACCEL_ZOUT_H 0x31
#define REG_ADD_ACCEL_ZOUT_L 0x32
#define REG_ADD_GYRO_XOUT_H 0x33
#define REG_ADD_GYRO_XOUT_L 0x34
#define REG_ADD_GYRO_YOUT_H 0x35
#define REG_ADD_GYRO_YOUT_L 0x36
#define REG_ADD_GYRO_ZOUT_H 0x37
#define REG_ADD_GYRO_ZOUT_L 0x38
#define REG_ADD_EXT_SENS_DATA_00 0x3B
#define REG_ADD_REG_BANK_SEL 0x7F
#define REG_VAL_REG_BANK_0 0x00
#define REG_VAL_REG_BANK_1 0x10
#define REG_VAL_REG_BANK_2 0x20
#define REG_VAL_REG_BANK_3 0x30
/* user bank 1 register */
/* user bank 2 register */
#define REG_ADD_GYRO_SMPLRT_DIV 0x00
#define REG_ADD_GYRO_CONFIG_1 0x01
#define REG_VAL_BIT_GYRO_DLPCFG_2 0x10 /* bit[5:3] */
#define REG_VAL_BIT_GYRO_DLPCFG_4 0x20 /* bit[5:3] */
#define REG_VAL_BIT_GYRO_DLPCFG_6 0x30 /* bit[5:3] */
#define REG_VAL_BIT_GYRO_FS_250DPS 0x00 /* bit[2:1] */
#define REG_VAL_BIT_GYRO_FS_500DPS 0x02 /* bit[2:1] */
#define REG_VAL_BIT_GYRO_FS_1000DPS 0x04 /* bit[2:1] */
#define REG_VAL_BIT_GYRO_FS_2000DPS 0x06 /* bit[2:1] */
#define REG_VAL_BIT_GYRO_DLPF 0x01 /* bit[0] */
#define REG_ADD_ACCEL_SMPLRT_DIV_2 0x11
#define REG_ADD_ACCEL_CONFIG 0x14
#define REG_VAL_BIT_ACCEL_DLPCFG_2 0x10 /* bit[5:3] */
#define REG_VAL_BIT_ACCEL_DLPCFG_4 0x20 /* bit[5:3] */
#define REG_VAL_BIT_ACCEL_DLPCFG_6 0x30 /* bit[5:3] */
#define REG_VAL_BIT_ACCEL_FS_2g 0x00 /* bit[2:1] */
#define REG_VAL_BIT_ACCEL_FS_4g 0x02 /* bit[2:1] */
#define REG_VAL_BIT_ACCEL_FS_8g 0x04 /* bit[2:1] */
#define REG_VAL_BIT_ACCEL_FS_16g 0x06 /* bit[2:1] */
#define REG_VAL_BIT_ACCEL_DLPF 0x01 /* bit[0] */
/* user bank 3 register */
#define REG_ADD_I2C_SLV0_ADDR 0x03
#define REG_ADD_I2C_SLV0_REG 0x04
#define REG_ADD_I2C_SLV0_CTRL 0x05
#define REG_VAL_BIT_SLV0_EN 0x80
#define REG_VAL_BIT_MASK_LEN 0x07
#define REG_ADD_I2C_SLV0_DO 0x06
#define REG_ADD_I2C_SLV1_ADDR 0x07
#define REG_ADD_I2C_SLV1_REG 0x08
#define REG_ADD_I2C_SLV1_CTRL 0x09
#define REG_ADD_I2C_SLV1_DO 0x0A
/* define ICM-20948 Register end */
/* define ICM-20948 MAG Register */
#define REG_ADD_MAG_WIA1 0x00
#define REG_VAL_MAG_WIA1 0x48
#define REG_ADD_MAG_WIA2 0x01
#define REG_VAL_MAG_WIA2 0x09
#define REG_ADD_MAG_ST2 0x10
#define REG_ADD_MAG_DATA 0x11
#define REG_ADD_MAG_CNTL2 0x31
#define REG_VAL_MAG_MODE_PD 0x00
#define REG_VAL_MAG_MODE_SM 0x01
#define REG_VAL_MAG_MODE_10HZ 0x02
#define REG_VAL_MAG_MODE_20HZ 0x04
#define REG_VAL_MAG_MODE_50HZ 0x05
#define REG_VAL_MAG_MODE_100HZ 0x08
#define REG_VAL_MAG_MODE_ST 0x10
/* define ICM-20948 MAG Register end */
#define MAG_DATA_LEN 6
#ifdef __cplusplus
extern "C" {
#endif
typedef enum
{
IMU_EN_SENSOR_TYPE_NULL = 0,
IMU_EN_SENSOR_TYPE_ICM20948,
IMU_EN_SENSOR_TYPE_MAX
}IMU_EN_SENSOR_TYPE;
typedef struct imu_st_angles_data_tag
{
float fYaw;
float fPitch;
float fRoll;
}IMU_ST_ANGLES_DATA;
typedef struct imu_st_sensor_data_tag
{
int16_t s16X;
int16_t s16Y;
int16_t s16Z;
}IMU_ST_SENSOR_DATA;
typedef struct icm20948_st_avg_data_tag
{
uint8_t u8Index;
int16_t s16AvgBuffer[8];
}ICM20948_ST_AVG_DATA;
void imuInit(IMU_EN_SENSOR_TYPE *penMotionSensorType);
void imuDataAccGyrGet(IMU_ST_SENSOR_DATA *pstGyroRawData,
IMU_ST_SENSOR_DATA *pstAccelRawData);
void imuDataGet(IMU_ST_ANGLES_DATA *pstAngles,
IMU_ST_SENSOR_DATA *pstGyroRawData,
IMU_ST_SENSOR_DATA *pstAccelRawData,
IMU_ST_SENSOR_DATA *pstMagnRawData);
void icm20948GyroRead(int16_t* ps16X, int16_t* ps16Y, int16_t* ps16Z);
void icm20948AccelRead(int16_t* ps16X, int16_t* ps16Y, int16_t* ps16Z);
void icm20948GyroFastRead(int16_t* ps16X, int16_t* ps16Y, int16_t* ps16Z);
void icm20948AccelFastRead(int16_t* ps16X, int16_t* ps16Y, int16_t* ps16Z);
char I2C_ReadOneByte(char reg);
void I2C_WriteOneByte(char reg, char val);
#ifdef __cplusplus
}
#endif
#endif
#include "icm20948.h"
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/time.h"
#include "pico/multicore.h" // only used by main_4
#include "pico/util/queue.h" // only used by main_4
int main_1(void)
{
stdio_init_all();
IMU_EN_SENSOR_TYPE enMotionSensorType;
IMU_ST_ANGLES_DATA stAngles;
IMU_ST_SENSOR_DATA stGyroRawData;
IMU_ST_SENSOR_DATA stAccelRawData;
IMU_ST_SENSOR_DATA stMagnRawData;
imuInit(&enMotionSensorType);
if (IMU_EN_SENSOR_TYPE_ICM20948 == enMotionSensorType) {
printf("Motion sensor is ICM-20948\n");
} else {
printf("Motion sensor NULL\n");
}
uint64_t t_prev = time_us_64();
while (1) {
imuDataGet(&stAngles, &stGyroRawData, &stAccelRawData, &stMagnRawData);
uint64_t t_now = time_us_64();
uint64_t dt_us = (t_now - t_prev);
t_prev = t_now;
float hz = (dt_us > 0) ? (1000000.0f / (float)dt_us) : 0.0f;
printf("\r\n/-------------------------------------------------------------/\r\n");
printf("Roll: %.2f Pitch: %.2f Yaw: %.2f | Sample Rate: %.1f Hz\r\n",
stAngles.fRoll, stAngles.fPitch, stAngles.fYaw, hz);
//sleep_ms(100);
}
return 0;
}
int main_2(void)
{
stdio_init_all();
IMU_EN_SENSOR_TYPE type;
IMU_ST_SENSOR_DATA g, a;
imuInit(&type);
if (IMU_EN_SENSOR_TYPE_ICM20948 == type) {
printf("Motion sensor is ICM-20948\n");
} else {
printf("Motion sensor NULL\n");
}
uint64_t t_prev = time_us_64();
while (1) {
int16_t gx, gy, gz, ax, ay, az;
icm20948AccelRead(&ax, &ay, &az);
icm20948GyroRead (&gx, &gy, &gz);
uint64_t t_now = time_us_64();
uint64_t dt_us = (t_now - t_prev);
t_prev = t_now;
float hz = (dt_us > 0) ? (1000000.0f / (float)dt_us) : 0.0f;
printf("ACC [mg-ish raw]: X=%d Y=%d Z=%d | GYRO [LSB]: X=%d Y=%d Z=%d | %.1f Hz\r\n",
ax, ay, az, gx, gy, gz, hz);
// sleep_ms(5); // small delay to avoid spamming
}
return 0;
}
int main_3(void)
{
stdio_init_all();
IMU_EN_SENSOR_TYPE type;
imuInit(&type);
if (IMU_EN_SENSOR_TYPE_ICM20948 == type) {
printf("Motion sensor is ICM-20948 (FAST read path)\n");
} else {
printf("Motion sensor NULL\n");
}
uint64_t t_prev = time_us_64();
while (1) {
int16_t gx, gy, gz, ax, ay, az;
// Option A: call the fast functions directly
icm20948AccelFastRead(&ax, &ay, &az);
icm20948GyroFastRead (&gx, &gy, &gz);
// // Option B: if you prefer the wrapper:
// IMU_ST_SENSOR_DATA g, a;
// imuDataOnlyGet(&g, &a);
// gx=g.s16X; gy=g.s16Y; gz=g.s16Z;
// ax=a.s16X; ay=a.s16Y; az=a.s16Z;
uint64_t t_now = time_us_64();
uint64_t dt_us = (t_now - t_prev);
t_prev = t_now;
float hz = (dt_us > 0) ? (1000000.0f / (float)dt_us) : 0.0f;
printf("FAST ACC: X=%d Y=%d Z=%d | FAST GYRO: X=%d Y=%d Z=%d | %.1f Hz\r\n",
ax, ay, az, gx, gy, gz, hz);
// No sleep => let it run as fast as possible
}
return 0;
}
typedef struct {
int16_t ax, ay, az;
int16_t gx, gy, gz;
uint32_t t_us; // timestamp (lower 32 bits is fine for short runs)
} Sample;
static queue_t sample_q;
static void core1_reader(void)
{
IMU_EN_SENSOR_TYPE type;
// Important: init I2C/IMU on this core as well if your drivers require per-core context.
// Usually one init on core0 is fine if both cores share the same hardware state,
// but to be safe we at least check sensor here.
// If needed, comment out the next line.
// imuInit(&type);
uint32_t t_prev = (uint32_t)time_us_64();
while (1) {
IMU_ST_SENSOR_DATA stGyroRawData, stAccelRawData;
// int16_t gx, gy, gz, ax, ay, az;
// icm20948AccelFastRead(&ax, &ay, &az);
// icm20948GyroFastRead (&gx, &gy, &gz);
imuDataAccGyrGet(&stGyroRawData, &stAccelRawData);
uint32_t t_now = (uint32_t)time_us_64();
Sample s = { stAccelRawData.s16X, stAccelRawData.s16Y, stAccelRawData.s16Z,
stGyroRawData.s16X, stGyroRawData.s16Y, stGyroRawData.s16Z,
t_now };
queue_add_blocking(&sample_q, &s);
// (Optional) pace the producer slightly if needed
// sleep_us(500); // ~2 kHz -> uncomment to throttle
}
}
int main_4(void)
{
stdio_init_all();
IMU_EN_SENSOR_TYPE type;
imuInit(&type);
if (IMU_EN_SENSOR_TYPE_ICM20948 == type) {
printf("Motion sensor is ICM-20948 (multicore)\n");
} else {
printf("Motion sensor NULL\n");
}
// Queue can hold up to N samples; adjust for your bandwidth
queue_init(&sample_q, sizeof(Sample), 64);
// Launch core1 reader
multicore_launch_core1(core1_reader);
uint32_t t_prev = (uint32_t)time_us_64();
while (1) {
Sample s;
queue_remove_blocking(&sample_q, &s);
uint32_t t_now = (uint32_t)time_us_64();
uint32_t dt_us = (t_now - t_prev);
t_prev = t_now;
float hz = (dt_us > 0) ? (1000000.0f / (float)dt_us) : 0.0f;
printf("ACC: X=%d Y=%d Z=%d | GYRO: X=%d Y=%d Z=%d | RX Rate: %.1f Hz\r\n",
s.ax, s.ay, s.az, s.gx, s.gy, s.gz, hz);
// No sleep: printing rate is now bounded mostly by USB/serial throughput.
}
return 0;
}
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
endif ()
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
endif()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
)
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
GIT_SUBMODULES_RECURSE FALSE
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
else ()
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
endif ()
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})
build
!.vscode/*
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.2.0)
set(toolchainVersion 14_2_Rel1)
set(picotoolVersion 2.2.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD pico CACHE STRING "Board type")
set(PROGRAM_NAME lcd_draw)
cmake_minimum_required(VERSION 3.12)
include(pico_sdk_import.cmake)
project(Pico-ResTouch-X_X_code)
pico_sdk_init()
add_subdirectory(lib/config)
add_subdirectory(lib/lcd)
add_subdirectory(lib/font)
include_directories(./lib/config)
include_directories(./lib/lcd)
include_directories(./lib/font)
add_executable(${PROGRAM_NAME}
main.c
)
# enable usb output, disable uart output
pico_enable_stdio_usb(${PROGRAM_NAME} 1)
pico_enable_stdio_uart(${PROGRAM_NAME} 0)
# create map/bin/hex/uf2 file etc.
pico_add_extra_outputs(${PROGRAM_NAME})
target_link_libraries(${PROGRAM_NAME}
lcd
font
config
pico_stdlib
hardware_spi
hardware_pwm
)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_Config_SRCS 变量
aux_source_directory(. DIR_CONFIG_SRCS)
# 生成链接库
add_library(config ${DIR_CONFIG_SRCS})
target_link_libraries(config PUBLIC pico_stdlib hardware_spi hardware_pwm)
/*****************************************************************************
* | File : DEV_Config.c
* | Author : Waveshare team
* | Function : Show SDcard BMP picto LCD
* | Info :
* Provide the hardware underlying interface
*----------------
* | This version: V1.0
* | Date : 2018-01-11
* | Info : Basic version
*
******************************************************************************/
#include "DEV_Config.h"
#include "pico/stdlib.h"
void DEV_Digital_Write(UWORD Pin, UBYTE Value)
{
gpio_put(Pin, Value);
}
UBYTE DEV_Digital_Read(UWORD Pin)
{
return gpio_get(Pin);
}
/**
* GPIO Mode
**/
void DEV_GPIO_Mode(UWORD Pin, UWORD Mode)
{
gpio_init(Pin);
if(Mode == 0 || Mode == GPIO_IN) {
gpio_set_dir(Pin, GPIO_IN);
} else {
gpio_set_dir(Pin, GPIO_OUT);
}
}
void DEV_GPIO_Init(void)
{
DEV_GPIO_Mode(LCD_RST_PIN,GPIO_OUT);
DEV_GPIO_Mode(LCD_DC_PIN, GPIO_OUT);
//DEV_GPIO_Mode(LCD_BKL_PIN, GPIO_OUT);
DEV_GPIO_Mode(LCD_CS_PIN, GPIO_OUT);
DEV_GPIO_Mode(TP_CS_PIN,GPIO_OUT);
DEV_GPIO_Mode(TP_IRQ_PIN,GPIO_IN);
DEV_GPIO_Mode(SD_CS_PIN,GPIO_OUT);
//gpio_set_pulls(TP_IRQ_PIN,true,false);
DEV_Digital_Write(TP_CS_PIN, 1);
DEV_Digital_Write(LCD_CS_PIN, 1);
//DEV_Digital_Write(LCD_BKL_PIN, 0);
DEV_Digital_Write(SD_CS_PIN, 1);
gpio_set_function(LCD_BKL_PIN, GPIO_FUNC_PWM);
}
/********************************************************************************
function: System Init
note:
Initialize the communication method
********************************************************************************/
uint8_t System_Init(void)
{
stdio_init_all();
DEV_GPIO_Init();
spi_init(SPI_PORT,5000000);
gpio_set_function(LCD_CLK_PIN,GPIO_FUNC_SPI);
gpio_set_function(LCD_MOSI_PIN,GPIO_FUNC_SPI);
gpio_set_function(LCD_MISO_PIN,GPIO_FUNC_SPI);
return 0;
}
void System_Exit(void)
{
}
/*********************************************
function: Hardware interface
note:
SPI4W_Write_Byte(value) :
Register hardware SPI
*********************************************/
uint8_t SPI4W_Write_Byte(uint8_t value)
{
uint8_t rxDat;
spi_write_read_blocking(spi1,&value,&rxDat,1);
return rxDat;
}
uint8_t SPI4W_Read_Byte(uint8_t value)
{
return SPI4W_Write_Byte(value);
}
/********************************************************************************
function: Delay function
note:
Driver_Delay_ms(xms) : Delay x ms
Driver_Delay_us(xus) : Delay x us
********************************************************************************/
void Driver_Delay_ms(uint32_t xms)
{
sleep_ms(xms);
}
void Driver_Delay_us(uint32_t xus)
{
int j;
for(j=xus; j > 0; j--);
}
/*****************************************************************************
* | File : DEV_Config.c
* | Author : Waveshare team
* | Function : GPIO Function
* | Info :
* Provide the hardware underlying interface
*----------------
* | This version: V1.0
* | Date : 2018-01-11
* | Info : Basic version
*
******************************************************************************/
#ifndef _DEV_CONFIG_H_
#define _DEV_CONFIG_H_
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "hardware/pwm.h"
#include "stdio.h"
#define UBYTE uint8_t
#define UWORD uint16_t
#define UDOUBLE uint32_t
#define LCD_RST_PIN 15
#define LCD_DC_PIN 8
#define LCD_CS_PIN 9
#define LCD_CLK_PIN 10
#define LCD_BKL_PIN 13
#define LCD_MOSI_PIN 11
#define LCD_MISO_PIN 12
#define TP_CS_PIN 16
#define TP_IRQ_PIN 17
#define SD_CS_PIN 22
#define SPI_PORT spi1
#define MAX_BMP_FILES 25
/*------------------------------------------------------------------------------------------------------*/
void DEV_Digital_Write(UWORD Pin, UBYTE Value);
UBYTE DEV_Digital_Read(UWORD Pin);
void DEV_GPIO_Mode(UWORD Pin, UWORD Mode);
void DEV_GPIO_Init(void);
uint8_t System_Init(void);
void System_Exit(void);
uint8_t SPI4W_Write_Byte(uint8_t value);
uint8_t SPI4W_Read_Byte(uint8_t value);
void Driver_Delay_ms(uint32_t xms);
void Driver_Delay_us(uint32_t xus);
#endif
aux_source_directory(. DIR_font_SRCS)
add_library(font ${DIR_font_SRCS})
target_link_libraries(font PUBLIC)
/**
******************************************************************************
* @file Font12.c
* @author MCD Application Team
* @version V1.0.0
* @date 18-February-2014
* @brief This file provides text Font12 for STM32xx-EVAL's LCD driver.
******************************************************************************
* @attention
*
* <h2><center>&copy; COPYRIGHT(c) 2014 STMicroelectronics</center></h2>
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of STMicroelectronics nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "fonts.h"
//
// Font data for Courier New 12pt
//
const uint8_t Font12_Table[] =
{
// @0 ' ' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
// @12 '!' (7 pixels wide)
0x00, //
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x00, //
0x00, //
0x10, // #
0x00, //
0x00, //
0x00, //
// @24 '"' (7 pixels wide)
0x00, //
0x6C, // ## ##
0x48, // # #
0x48, // # #
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
// @36 '#' (7 pixels wide)
0x00, //
0x14, // # #
0x14, // # #
0x28, // # #
0x7C, // #####
0x28, // # #
0x7C, // #####
0x28, // # #
0x50, // # #
0x50, // # #
0x00, //
0x00, //
// @48 '$' (7 pixels wide)
0x00, //
0x10, // #
0x38, // ###
0x40, // #
0x40, // #
0x38, // ###
0x48, // # #
0x70, // ###
0x10, // #
0x10, // #
0x00, //
0x00, //
// @60 '%' (7 pixels wide)
0x00, //
0x20, // #
0x50, // # #
0x20, // #
0x0C, // ##
0x70, // ###
0x08, // #
0x14, // # #
0x08, // #
0x00, //
0x00, //
0x00, //
// @72 '&' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x18, // ##
0x20, // #
0x20, // #
0x54, // # # #
0x48, // # #
0x34, // ## #
0x00, //
0x00, //
0x00, //
// @84 ''' (7 pixels wide)
0x00, //
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
// @96 '(' (7 pixels wide)
0x00, //
0x08, // #
0x08, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x08, // #
0x08, // #
0x00, //
// @108 ')' (7 pixels wide)
0x00, //
0x20, // #
0x20, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x20, // #
0x20, // #
0x00, //
// @120 '*' (7 pixels wide)
0x00, //
0x10, // #
0x7C, // #####
0x10, // #
0x28, // # #
0x28, // # #
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
// @132 '+' (7 pixels wide)
0x00, //
0x00, //
0x10, // #
0x10, // #
0x10, // #
0xFE, // #######
0x10, // #
0x10, // #
0x10, // #
0x00, //
0x00, //
0x00, //
// @144 ',' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x18, // ##
0x10, // #
0x30, // ##
0x20, // #
0x00, //
// @156 '-' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x7C, // #####
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
// @168 '.' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x30, // ##
0x30, // ##
0x00, //
0x00, //
0x00, //
// @180 '/' (7 pixels wide)
0x00, //
0x04, // #
0x04, // #
0x08, // #
0x08, // #
0x10, // #
0x10, // #
0x20, // #
0x20, // #
0x40, // #
0x00, //
0x00, //
// @192 '0' (7 pixels wide)
0x00, //
0x38, // ###
0x44, // # #
0x44, // # #
0x44, // # #
0x44, // # #
0x44, // # #
0x44, // # #
0x38, // ###
0x00, //
0x00, //
0x00, //
// @204 '1' (7 pixels wide)
0x00, //
0x30, // ##
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x7C, // #####
0x00, //
0x00, //
0x00, //
// @216 '2' (7 pixels wide)
0x00, //
0x38, // ###
0x44, // # #
0x04, // #
0x08, // #
0x10, // #
0x20, // #
0x44, // # #
0x7C, // #####
0x00, //
0x00, //
0x00, //
// @228 '3' (7 pixels wide)
0x00, //
0x38, // ###
0x44, // # #
0x04, // #
0x18, // ##
0x04, // #
0x04, // #
0x44, // # #
0x38, // ###
0x00, //
0x00, //
0x00, //
// @240 '4' (7 pixels wide)
0x00, //
0x0C, // ##
0x14, // # #
0x14, // # #
0x24, // # #
0x44, // # #
0x7E, // ######
0x04, // #
0x0E, // ###
0x00, //
0x00, //
0x00, //
// @252 '5' (7 pixels wide)
0x00, //
0x3C, // ####
0x20, // #
0x20, // #
0x38, // ###
0x04, // #
0x04, // #
0x44, // # #
0x38, // ###
0x00, //
0x00, //
0x00, //
// @264 '6' (7 pixels wide)
0x00, //
0x1C, // ###
0x20, // #
0x40, // #
0x78, // ####
0x44, // # #
0x44, // # #
0x44, // # #
0x38, // ###
0x00, //
0x00, //
0x00, //
// @276 '7' (7 pixels wide)
0x00, //
0x7C, // #####
0x44, // # #
0x04, // #
0x08, // #
0x08, // #
0x08, // #
0x10, // #
0x10, // #
0x00, //
0x00, //
0x00, //
// @288 '8' (7 pixels wide)
0x00, //
0x38, // ###
0x44, // # #
0x44, // # #
0x38, // ###
0x44, // # #
0x44, // # #
0x44, // # #
0x38, // ###
0x00, //
0x00, //
0x00, //
// @300 '9' (7 pixels wide)
0x00, //
0x38, // ###
0x44, // # #
0x44, // # #
0x44, // # #
0x3C, // ####
0x04, // #
0x08, // #
0x70, // ###
0x00, //
0x00, //
0x00, //
// @312 ':' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x30, // ##
0x30, // ##
0x00, //
0x00, //
0x30, // ##
0x30, // ##
0x00, //
0x00, //
0x00, //
// @324 ';' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x18, // ##
0x18, // ##
0x00, //
0x00, //
0x18, // ##
0x30, // ##
0x20, // #
0x00, //
0x00, //
// @336 '<' (7 pixels wide)
0x00, //
0x00, //
0x0C, // ##
0x10, // #
0x60, // ##
0x80, // #
0x60, // ##
0x10, // #
0x0C, // ##
0x00, //
0x00, //
0x00, //
// @348 '=' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x00, //
0x7C, // #####
0x00, //
0x7C, // #####
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
// @360 '>' (7 pixels wide)
0x00, //
0x00, //
0xC0, // ##
0x20, // #
0x18, // ##
0x04, // #
0x18, // ##
0x20, // #
0xC0, // ##
0x00, //
0x00, //
0x00, //
// @372 '?' (7 pixels wide)
0x00, //
0x00, //
0x18, // ##
0x24, // # #
0x04, // #
0x08, // #
0x10, // #
0x00, //
0x30, // ##
0x00, //
0x00, //
0x00, //
// @384 '@' (7 pixels wide)
0x38, // ###
0x44, // # #
0x44, // # #
0x4C, // # ##
0x54, // # # #
0x54, // # # #
0x4C, // # ##
0x40, // #
0x44, // # #
0x38, // ###
0x00, //
0x00, //
// @396 'A' (7 pixels wide)
0x00, //
0x30, // ##
0x10, // #
0x28, // # #
0x28, // # #
0x28, // # #
0x7C, // #####
0x44, // # #
0xEE, // ### ###
0x00, //
0x00, //
0x00, //
// @408 'B' (7 pixels wide)
0x00, //
0xF8, // #####
0x44, // # #
0x44, // # #
0x78, // ####
0x44, // # #
0x44, // # #
0x44, // # #
0xF8, // #####
0x00, //
0x00, //
0x00, //
// @420 'C' (7 pixels wide)
0x00, //
0x3C, // ####
0x44, // # #
0x40, // #
0x40, // #
0x40, // #
0x40, // #
0x44, // # #
0x38, // ###
0x00, //
0x00, //
0x00, //
// @432 'D' (7 pixels wide)
0x00, //
0xF0, // ####
0x48, // # #
0x44, // # #
0x44, // # #
0x44, // # #
0x44, // # #
0x48, // # #
0xF0, // ####
0x00, //
0x00, //
0x00, //
// @444 'E' (7 pixels wide)
0x00, //
0xFC, // ######
0x44, // # #
0x50, // # #
0x70, // ###
0x50, // # #
0x40, // #
0x44, // # #
0xFC, // ######
0x00, //
0x00, //
0x00, //
// @456 'F' (7 pixels wide)
0x00, //
0x7E, // ######
0x22, // # #
0x28, // # #
0x38, // ###
0x28, // # #
0x20, // #
0x20, // #
0x70, // ###
0x00, //
0x00, //
0x00, //
// @468 'G' (7 pixels wide)
0x00, //
0x3C, // ####
0x44, // # #
0x40, // #
0x40, // #
0x4E, // # ###
0x44, // # #
0x44, // # #
0x38, // ###
0x00, //
0x00, //
0x00, //
// @480 'H' (7 pixels wide)
0x00, //
0xEE, // ### ###
0x44, // # #
0x44, // # #
0x7C, // #####
0x44, // # #
0x44, // # #
0x44, // # #
0xEE, // ### ###
0x00, //
0x00, //
0x00, //
// @492 'I' (7 pixels wide)
0x00, //
0x7C, // #####
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x7C, // #####
0x00, //
0x00, //
0x00, //
// @504 'J' (7 pixels wide)
0x00, //
0x3C, // ####
0x08, // #
0x08, // #
0x08, // #
0x48, // # #
0x48, // # #
0x48, // # #
0x30, // ##
0x00, //
0x00, //
0x00, //
// @516 'K' (7 pixels wide)
0x00, //
0xEE, // ### ###
0x44, // # #
0x48, // # #
0x50, // # #
0x70, // ###
0x48, // # #
0x44, // # #
0xE6, // ### ##
0x00, //
0x00, //
0x00, //
// @528 'L' (7 pixels wide)
0x00, //
0x70, // ###
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x24, // # #
0x24, // # #
0x7C, // #####
0x00, //
0x00, //
0x00, //
// @540 'M' (7 pixels wide)
0x00, //
0xEE, // ### ###
0x6C, // ## ##
0x6C, // ## ##
0x54, // # # #
0x54, // # # #
0x44, // # #
0x44, // # #
0xEE, // ### ###
0x00, //
0x00, //
0x00, //
// @552 'N' (7 pixels wide)
0x00, //
0xEE, // ### ###
0x64, // ## #
0x64, // ## #
0x54, // # # #
0x54, // # # #
0x54, // # # #
0x4C, // # ##
0xEC, // ### ##
0x00, //
0x00, //
0x00, //
// @564 'O' (7 pixels wide)
0x00, //
0x38, // ###
0x44, // # #
0x44, // # #
0x44, // # #
0x44, // # #
0x44, // # #
0x44, // # #
0x38, // ###
0x00, //
0x00, //
0x00, //
// @576 'P' (7 pixels wide)
0x00, //
0x78, // ####
0x24, // # #
0x24, // # #
0x24, // # #
0x38, // ###
0x20, // #
0x20, // #
0x70, // ###
0x00, //
0x00, //
0x00, //
// @588 'Q' (7 pixels wide)
0x00, //
0x38, // ###
0x44, // # #
0x44, // # #
0x44, // # #
0x44, // # #
0x44, // # #
0x44, // # #
0x38, // ###
0x1C, // ###
0x00, //
0x00, //
// @600 'R' (7 pixels wide)
0x00, //
0xF8, // #####
0x44, // # #
0x44, // # #
0x44, // # #
0x78, // ####
0x48, // # #
0x44, // # #
0xE2, // ### #
0x00, //
0x00, //
0x00, //
// @612 'S' (7 pixels wide)
0x00, //
0x34, // ## #
0x4C, // # ##
0x40, // #
0x38, // ###
0x04, // #
0x04, // #
0x64, // ## #
0x58, // # ##
0x00, //
0x00, //
0x00, //
// @624 'T' (7 pixels wide)
0x00, //
0xFE, // #######
0x92, // # # #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x38, // ###
0x00, //
0x00, //
0x00, //
// @636 'U' (7 pixels wide)
0x00, //
0xEE, // ### ###
0x44, // # #
0x44, // # #
0x44, // # #
0x44, // # #
0x44, // # #
0x44, // # #
0x38, // ###
0x00, //
0x00, //
0x00, //
// @648 'V' (7 pixels wide)
0x00, //
0xEE, // ### ###
0x44, // # #
0x44, // # #
0x28, // # #
0x28, // # #
0x28, // # #
0x10, // #
0x10, // #
0x00, //
0x00, //
0x00, //
// @660 'W' (7 pixels wide)
0x00, //
0xEE, // ### ###
0x44, // # #
0x44, // # #
0x54, // # # #
0x54, // # # #
0x54, // # # #
0x54, // # # #
0x28, // # #
0x00, //
0x00, //
0x00, //
// @672 'X' (7 pixels wide)
0x00, //
0xC6, // ## ##
0x44, // # #
0x28, // # #
0x10, // #
0x10, // #
0x28, // # #
0x44, // # #
0xC6, // ## ##
0x00, //
0x00, //
0x00, //
// @684 'Y' (7 pixels wide)
0x00, //
0xEE, // ### ###
0x44, // # #
0x28, // # #
0x28, // # #
0x10, // #
0x10, // #
0x10, // #
0x38, // ###
0x00, //
0x00, //
0x00, //
// @696 'Z' (7 pixels wide)
0x00, //
0x7C, // #####
0x44, // # #
0x08, // #
0x10, // #
0x10, // #
0x20, // #
0x44, // # #
0x7C, // #####
0x00, //
0x00, //
0x00, //
// @708 '[' (7 pixels wide)
0x00, //
0x38, // ###
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x38, // ###
0x00, //
// @720 '\' (7 pixels wide)
0x00, //
0x40, // #
0x20, // #
0x20, // #
0x20, // #
0x10, // #
0x10, // #
0x08, // #
0x08, // #
0x08, // #
0x00, //
0x00, //
// @732 ']' (7 pixels wide)
0x00, //
0x38, // ###
0x08, // #
0x08, // #
0x08, // #
0x08, // #
0x08, // #
0x08, // #
0x08, // #
0x08, // #
0x38, // ###
0x00, //
// @744 '^' (7 pixels wide)
0x00, //
0x10, // #
0x10, // #
0x28, // # #
0x44, // # #
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
// @756 '_' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0xFE, // #######
// @768 '`' (7 pixels wide)
0x00, //
0x10, // #
0x08, // #
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
// @780 'a' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x38, // ###
0x44, // # #
0x3C, // ####
0x44, // # #
0x44, // # #
0x3E, // #####
0x00, //
0x00, //
0x00, //
// @792 'b' (7 pixels wide)
0x00, //
0xC0, // ##
0x40, // #
0x58, // # ##
0x64, // ## #
0x44, // # #
0x44, // # #
0x44, // # #
0xF8, // #####
0x00, //
0x00, //
0x00, //
// @804 'c' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x3C, // ####
0x44, // # #
0x40, // #
0x40, // #
0x44, // # #
0x38, // ###
0x00, //
0x00, //
0x00, //
// @816 'd' (7 pixels wide)
0x00, //
0x0C, // ##
0x04, // #
0x34, // ## #
0x4C, // # ##
0x44, // # #
0x44, // # #
0x44, // # #
0x3E, // #####
0x00, //
0x00, //
0x00, //
// @828 'e' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x38, // ###
0x44, // # #
0x7C, // #####
0x40, // #
0x40, // #
0x3C, // ####
0x00, //
0x00, //
0x00, //
// @840 'f' (7 pixels wide)
0x00, //
0x1C, // ###
0x20, // #
0x7C, // #####
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x7C, // #####
0x00, //
0x00, //
0x00, //
// @852 'g' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x36, // ## ##
0x4C, // # ##
0x44, // # #
0x44, // # #
0x44, // # #
0x3C, // ####
0x04, // #
0x38, // ###
0x00, //
// @864 'h' (7 pixels wide)
0x00, //
0xC0, // ##
0x40, // #
0x58, // # ##
0x64, // ## #
0x44, // # #
0x44, // # #
0x44, // # #
0xEE, // ### ###
0x00, //
0x00, //
0x00, //
// @876 'i' (7 pixels wide)
0x00, //
0x10, // #
0x00, //
0x70, // ###
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x7C, // #####
0x00, //
0x00, //
0x00, //
// @888 'j' (7 pixels wide)
0x00, //
0x10, // #
0x00, //
0x78, // ####
0x08, // #
0x08, // #
0x08, // #
0x08, // #
0x08, // #
0x08, // #
0x70, // ###
0x00, //
// @900 'k' (7 pixels wide)
0x00, //
0xC0, // ##
0x40, // #
0x5C, // # ###
0x48, // # #
0x70, // ###
0x50, // # #
0x48, // # #
0xDC, // ## ###
0x00, //
0x00, //
0x00, //
// @912 'l' (7 pixels wide)
0x00, //
0x30, // ##
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x7C, // #####
0x00, //
0x00, //
0x00, //
// @924 'm' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0xE8, // ### #
0x54, // # # #
0x54, // # # #
0x54, // # # #
0x54, // # # #
0xFE, // #######
0x00, //
0x00, //
0x00, //
// @936 'n' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0xD8, // ## ##
0x64, // ## #
0x44, // # #
0x44, // # #
0x44, // # #
0xEE, // ### ###
0x00, //
0x00, //
0x00, //
// @948 'o' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x38, // ###
0x44, // # #
0x44, // # #
0x44, // # #
0x44, // # #
0x38, // ###
0x00, //
0x00, //
0x00, //
// @960 'p' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0xD8, // ## ##
0x64, // ## #
0x44, // # #
0x44, // # #
0x44, // # #
0x78, // ####
0x40, // #
0xE0, // ###
0x00, //
// @972 'q' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x36, // ## ##
0x4C, // # ##
0x44, // # #
0x44, // # #
0x44, // # #
0x3C, // ####
0x04, // #
0x0E, // ###
0x00, //
// @984 'r' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x6C, // ## ##
0x30, // ##
0x20, // #
0x20, // #
0x20, // #
0x7C, // #####
0x00, //
0x00, //
0x00, //
// @996 's' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x3C, // ####
0x44, // # #
0x38, // ###
0x04, // #
0x44, // # #
0x78, // ####
0x00, //
0x00, //
0x00, //
// @1008 't' (7 pixels wide)
0x00, //
0x00, //
0x20, // #
0x7C, // #####
0x20, // #
0x20, // #
0x20, // #
0x22, // # #
0x1C, // ###
0x00, //
0x00, //
0x00, //
// @1020 'u' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0xCC, // ## ##
0x44, // # #
0x44, // # #
0x44, // # #
0x4C, // # ##
0x36, // ## ##
0x00, //
0x00, //
0x00, //
// @1032 'v' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0xEE, // ### ###
0x44, // # #
0x44, // # #
0x28, // # #
0x28, // # #
0x10, // #
0x00, //
0x00, //
0x00, //
// @1044 'w' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0xEE, // ### ###
0x44, // # #
0x54, // # # #
0x54, // # # #
0x54, // # # #
0x28, // # #
0x00, //
0x00, //
0x00, //
// @1056 'x' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0xCC, // ## ##
0x48, // # #
0x30, // ##
0x30, // ##
0x48, // # #
0xCC, // ## ##
0x00, //
0x00, //
0x00, //
// @1068 'y' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0xEE, // ### ###
0x44, // # #
0x24, // # #
0x28, // # #
0x18, // ##
0x10, // #
0x10, // #
0x78, // ####
0x00, //
// @1080 'z' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x7C, // #####
0x48, // # #
0x10, // #
0x20, // #
0x44, // # #
0x7C, // #####
0x00, //
0x00, //
0x00, //
// @1092 '{' (7 pixels wide)
0x00, //
0x08, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x20, // #
0x10, // #
0x10, // #
0x10, // #
0x08, // #
0x00, //
// @1104 '|' (7 pixels wide)
0x00, //
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x00, //
0x00, //
// @1116 '}' (7 pixels wide)
0x00, //
0x20, // #
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x08, // #
0x10, // #
0x10, // #
0x10, // #
0x20, // #
0x00, //
// @1128 '~' (7 pixels wide)
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x24, // # #
0x58, // # ##
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
};
sFONT Font12 = {
Font12_Table,
7, /* Width */
12, /* Height */
};
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
/**
******************************************************************************
* @file font16.c
* @author MCD Application Team
* @version V1.0.0
* @date 18-February-2014
* @brief This file provides text font16 for STM32xx-EVAL's LCD driver.
******************************************************************************
* @attention
*
* <h2><center>&copy; COPYRIGHT(c) 2014 STMicroelectronics</center></h2>
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of STMicroelectronics nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "fonts.h"
//
// Font data for Courier New 12pt
//
const uint8_t Font16_Table[] =
{
// @0 ' ' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @32 '!' (11 pixels wide)
0x00, 0x00, //
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x00, 0x00, //
0x0C, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @64 '"' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x1D, 0xC0, // ### ###
0x1D, 0xC0, // ### ###
0x08, 0x80, // # #
0x08, 0x80, // # #
0x08, 0x80, // # #
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @96 '#' (11 pixels wide)
0x00, 0x00, //
0x0D, 0x80, // ## ##
0x0D, 0x80, // ## ##
0x0D, 0x80, // ## ##
0x0D, 0x80, // ## ##
0x3F, 0xC0, // ########
0x1B, 0x00, // ## ##
0x3F, 0xC0, // ########
0x1B, 0x00, // ## ##
0x1B, 0x00, // ## ##
0x1B, 0x00, // ## ##
0x1B, 0x00, // ## ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @128 '$' (11 pixels wide)
0x04, 0x00, // #
0x1F, 0x80, // ######
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x38, 0x00, // ###
0x1E, 0x00, // ####
0x0F, 0x00, // ####
0x03, 0x80, // ###
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x3F, 0x00, // ######
0x04, 0x00, // #
0x04, 0x00, // #
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @160 '%' (11 pixels wide)
0x00, 0x00, //
0x18, 0x00, // ##
0x24, 0x00, // # #
0x24, 0x00, // # #
0x18, 0xC0, // ## ##
0x07, 0x80, // ####
0x1E, 0x00, // ####
0x31, 0x80, // ## ##
0x02, 0x40, // # #
0x02, 0x40, // # #
0x01, 0x80, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @192 '&' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x0F, 0x00, // ####
0x18, 0x00, // ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0x0C, 0x00, // ##
0x1D, 0x80, // ### ##
0x37, 0x00, // ## ###
0x33, 0x00, // ## ##
0x1D, 0x80, // ### ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @224 ''' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x07, 0x00, // ###
0x07, 0x00, // ###
0x02, 0x00, // #
0x02, 0x00, // #
0x02, 0x00, // #
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @256 '(' (11 pixels wide)
0x00, 0x00, //
0x03, 0x00, // ##
0x03, 0x00, // ##
0x06, 0x00, // ##
0x0E, 0x00, // ###
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0E, 0x00, // ###
0x06, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @288 ')' (11 pixels wide)
0x00, 0x00, //
0x18, 0x00, // ##
0x18, 0x00, // ##
0x0C, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x0C, 0x00, // ##
0x1C, 0x00, // ###
0x18, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @320 '*' (11 pixels wide)
0x00, 0x00, //
0x06, 0x00, // ##
0x06, 0x00, // ##
0x3F, 0xC0, // ########
0x3F, 0xC0, // ########
0x0F, 0x00, // ####
0x1F, 0x80, // ######
0x19, 0x80, // ## ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @352 '+' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x04, 0x00, // #
0x04, 0x00, // #
0x04, 0x00, // #
0x3F, 0x80, // #######
0x04, 0x00, // #
0x04, 0x00, // #
0x04, 0x00, // #
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @384 ',' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x06, 0x00, // ##
0x04, 0x00, // #
0x0C, 0x00, // ##
0x08, 0x00, // #
0x08, 0x00, // #
0x00, 0x00, //
0x00, 0x00, //
// @416 '-' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x3F, 0x80, // #######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @448 '.' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @480 '/' (11 pixels wide)
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x01, 0x80, // ##
0x01, 0x80, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x06, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0x30, 0x00, // ##
0x30, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @512 '0' (11 pixels wide)
0x00, 0x00, //
0x0E, 0x00, // ###
0x1B, 0x00, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x1B, 0x00, // ## ##
0x0E, 0x00, // ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @544 '1' (11 pixels wide)
0x00, 0x00, //
0x06, 0x00, // ##
0x3E, 0x00, // #####
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x3F, 0xC0, // ########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @576 '2' (11 pixels wide)
0x00, 0x00, //
0x0F, 0x00, // ####
0x19, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x03, 0x00, // ##
0x06, 0x00, // ##
0x0C, 0x00, // ##
0x18, 0x00, // ##
0x30, 0x00, // ##
0x3F, 0x80, // #######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @608 '3' (11 pixels wide)
0x00, 0x00, //
0x3F, 0x00, // ######
0x61, 0x80, // ## ##
0x01, 0x80, // ##
0x03, 0x00, // ##
0x1F, 0x00, // #####
0x03, 0x80, // ###
0x01, 0x80, // ##
0x01, 0x80, // ##
0x61, 0x80, // ## ##
0x3F, 0x00, // ######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @640 '4' (11 pixels wide)
0x00, 0x00, //
0x07, 0x00, // ###
0x07, 0x00, // ###
0x0F, 0x00, // ####
0x0B, 0x00, // # ##
0x1B, 0x00, // ## ##
0x13, 0x00, // # ##
0x33, 0x00, // ## ##
0x3F, 0x80, // #######
0x03, 0x00, // ##
0x0F, 0x80, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @672 '5' (11 pixels wide)
0x00, 0x00, //
0x1F, 0x80, // ######
0x18, 0x00, // ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0x1F, 0x00, // #####
0x11, 0x80, // # ##
0x01, 0x80, // ##
0x01, 0x80, // ##
0x21, 0x80, // # ##
0x1F, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @704 '6' (11 pixels wide)
0x00, 0x00, //
0x07, 0x80, // ####
0x1C, 0x00, // ###
0x18, 0x00, // ##
0x30, 0x00, // ##
0x37, 0x00, // ## ###
0x39, 0x80, // ### ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x19, 0x80, // ## ##
0x0F, 0x00, // ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @736 '7' (11 pixels wide)
0x00, 0x00, //
0x7F, 0x00, // #######
0x43, 0x00, // # ##
0x03, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @768 '8' (11 pixels wide)
0x00, 0x00, //
0x1F, 0x00, // #####
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x1F, 0x00, // #####
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x1F, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @800 '9' (11 pixels wide)
0x00, 0x00, //
0x1E, 0x00, // ####
0x33, 0x00, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x33, 0x80, // ## ###
0x1D, 0x80, // ### ##
0x01, 0x80, // ##
0x03, 0x00, // ##
0x07, 0x00, // ###
0x3C, 0x00, // ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @832 ':' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @864 ';' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x03, 0x00, // ##
0x03, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x06, 0x00, // ##
0x04, 0x00, // #
0x08, 0x00, // #
0x08, 0x00, // #
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @896 '<' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0xC0, // ##
0x03, 0x00, // ##
0x04, 0x00, // #
0x18, 0x00, // ##
0x60, 0x00, // ##
0x18, 0x00, // ##
0x04, 0x00, // #
0x03, 0x00, // ##
0x00, 0xC0, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @928 '=' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x7F, 0xC0, // #########
0x00, 0x00, //
0x7F, 0xC0, // #########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @960 '>' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x60, 0x00, // ##
0x18, 0x00, // ##
0x04, 0x00, // #
0x03, 0x00, // ##
0x00, 0xC0, // ##
0x03, 0x00, // ##
0x04, 0x00, // #
0x18, 0x00, // ##
0x60, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @992 '?' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0x00, // #####
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x01, 0x80, // ##
0x07, 0x00, // ###
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x00, 0x00, //
0x0C, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1024 '@' (11 pixels wide)
0x00, 0x00, //
0x0E, 0x00, // ###
0x11, 0x00, // # #
0x21, 0x00, // # #
0x21, 0x00, // # #
0x27, 0x00, // # ###
0x29, 0x00, // # # #
0x29, 0x00, // # # #
0x27, 0x00, // # ###
0x20, 0x00, // #
0x11, 0x00, // # #
0x0E, 0x00, // ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1056 'A' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x3F, 0x00, // ######
0x0F, 0x00, // ####
0x09, 0x00, // # #
0x19, 0x80, // ## ##
0x19, 0x80, // ## ##
0x1F, 0x80, // ######
0x30, 0xC0, // ## ##
0x30, 0xC0, // ## ##
0x79, 0xE0, // #### ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1088 'B' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x7F, 0x00, // #######
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x3F, 0x00, // ######
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x7F, 0x00, // #######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1120 'C' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0x40, // ##### #
0x30, 0xC0, // ## ##
0x60, 0x40, // ## #
0x60, 0x00, // ##
0x60, 0x00, // ##
0x60, 0x00, // ##
0x60, 0x40, // ## #
0x30, 0x80, // ## #
0x1F, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1152 'D' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x7F, 0x00, // #######
0x31, 0x80, // ## ##
0x30, 0xC0, // ## ##
0x30, 0xC0, // ## ##
0x30, 0xC0, // ## ##
0x30, 0xC0, // ## ##
0x30, 0xC0, // ## ##
0x31, 0x80, // ## ##
0x7F, 0x00, // #######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1184 'E' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x7F, 0x80, // ########
0x30, 0x80, // ## #
0x30, 0x80, // ## #
0x32, 0x00, // ## #
0x3E, 0x00, // #####
0x32, 0x00, // ## #
0x30, 0x80, // ## #
0x30, 0x80, // ## #
0x7F, 0x80, // ########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1216 'F' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x7F, 0xC0, // #########
0x30, 0x40, // ## #
0x30, 0x40, // ## #
0x32, 0x00, // ## #
0x3E, 0x00, // #####
0x32, 0x00, // ## #
0x30, 0x00, // ##
0x30, 0x00, // ##
0x7C, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1248 'G' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x1E, 0x80, // #### #
0x31, 0x80, // ## ##
0x60, 0x80, // ## #
0x60, 0x00, // ##
0x60, 0x00, // ##
0x67, 0xC0, // ## #####
0x61, 0x80, // ## ##
0x31, 0x80, // ## ##
0x1F, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1280 'H' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x7B, 0xC0, // #### ####
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x3F, 0x80, // #######
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x7B, 0xC0, // #### ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1312 'I' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x3F, 0xC0, // ########
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x3F, 0xC0, // ########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1344 'J' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0xC0, // #######
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x63, 0x00, // ## ##
0x63, 0x00, // ## ##
0x63, 0x00, // ## ##
0x3E, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1376 'K' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x7B, 0xC0, // #### ####
0x31, 0x80, // ## ##
0x33, 0x00, // ## ##
0x36, 0x00, // ## ##
0x3C, 0x00, // ####
0x3E, 0x00, // #####
0x33, 0x00, // ## ##
0x31, 0x80, // ## ##
0x79, 0xC0, // #### ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1408 'L' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x7E, 0x00, // ######
0x18, 0x00, // ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0x18, 0x40, // ## #
0x18, 0x40, // ## #
0x18, 0x40, // ## #
0x7F, 0xC0, // #########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1440 'M' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0xE0, 0xE0, // ### ###
0x60, 0xC0, // ## ##
0x71, 0xC0, // ### ###
0x7B, 0xC0, // #### ####
0x6A, 0xC0, // ## # # ##
0x6E, 0xC0, // ## ### ##
0x64, 0xC0, // ## # ##
0x60, 0xC0, // ## ##
0xFB, 0xE0, // ##### #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1472 'N' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x73, 0xC0, // ### ####
0x31, 0x80, // ## ##
0x39, 0x80, // ### ##
0x3D, 0x80, // #### ##
0x35, 0x80, // ## # ##
0x37, 0x80, // ## ####
0x33, 0x80, // ## ###
0x31, 0x80, // ## ##
0x79, 0x80, // #### ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1504 'O' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0x00, // #####
0x31, 0x80, // ## ##
0x60, 0xC0, // ## ##
0x60, 0xC0, // ## ##
0x60, 0xC0, // ## ##
0x60, 0xC0, // ## ##
0x60, 0xC0, // ## ##
0x31, 0x80, // ## ##
0x1F, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1536 'P' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x7F, 0x00, // #######
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x3F, 0x00, // ######
0x30, 0x00, // ##
0x30, 0x00, // ##
0x7E, 0x00, // ######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1568 'Q' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0x00, // #####
0x31, 0x80, // ## ##
0x60, 0xC0, // ## ##
0x60, 0xC0, // ## ##
0x60, 0xC0, // ## ##
0x60, 0xC0, // ## ##
0x60, 0xC0, // ## ##
0x31, 0x80, // ## ##
0x1F, 0x00, // #####
0x0C, 0xC0, // ## ##
0x1F, 0x80, // ######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1600 'R' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x7F, 0x00, // #######
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x3E, 0x00, // #####
0x33, 0x00, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x7C, 0xE0, // ##### ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1632 'S' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0x80, // ######
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x38, 0x00, // ###
0x1F, 0x00, // #####
0x03, 0x80, // ###
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x3F, 0x00, // ######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1664 'T' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x7F, 0x80, // ########
0x4C, 0x80, // # ## #
0x4C, 0x80, // # ## #
0x4C, 0x80, // # ## #
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x3F, 0x00, // ######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1696 'U' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x7B, 0xC0, // #### ####
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x1F, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1728 'V' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x7B, 0xC0, // #### ####
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x1B, 0x00, // ## ##
0x1B, 0x00, // ## ##
0x1B, 0x00, // ## ##
0x0A, 0x00, // # #
0x0E, 0x00, // ###
0x0E, 0x00, // ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1760 'W' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0xFB, 0xE0, // ##### #####
0x60, 0xC0, // ## ##
0x64, 0xC0, // ## # ##
0x6E, 0xC0, // ## ### ##
0x6E, 0xC0, // ## ### ##
0x2A, 0x80, // # # # #
0x3B, 0x80, // ### ###
0x3B, 0x80, // ### ###
0x31, 0x80, // ## ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1792 'X' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x7B, 0xC0, // #### ####
0x31, 0x80, // ## ##
0x1B, 0x00, // ## ##
0x0E, 0x00, // ###
0x0E, 0x00, // ###
0x0E, 0x00, // ###
0x1B, 0x00, // ## ##
0x31, 0x80, // ## ##
0x7B, 0xC0, // #### ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1824 'Y' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x79, 0xE0, // #### ####
0x30, 0xC0, // ## ##
0x19, 0x80, // ## ##
0x0F, 0x00, // ####
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x1F, 0x80, // ######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1856 'Z' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x3F, 0x80, // #######
0x21, 0x80, // # ##
0x23, 0x00, // # ##
0x06, 0x00, // ##
0x04, 0x00, // #
0x0C, 0x00, // ##
0x18, 0x80, // ## #
0x30, 0x80, // ## #
0x3F, 0x80, // #######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1888 '[' (11 pixels wide)
0x00, 0x00, //
0x07, 0x80, // ####
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x07, 0x80, // ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1920 '\' (11 pixels wide)
0x30, 0x00, // ##
0x30, 0x00, // ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x06, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x01, 0x80, // ##
0x01, 0x80, // ##
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1952 ']' (11 pixels wide)
0x00, 0x00, //
0x1E, 0x00, // ####
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x1E, 0x00, // ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1984 '^' (11 pixels wide)
0x04, 0x00, // #
0x0A, 0x00, // # #
0x0A, 0x00, // # #
0x11, 0x00, // # #
0x20, 0x80, // # #
0x20, 0x80, // # #
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2016 '_' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0xFF, 0xE0, // ###########
// @2048 '`' (11 pixels wide)
0x08, 0x00, // #
0x04, 0x00, // #
0x02, 0x00, // #
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2080 'a' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0x00, // #####
0x01, 0x80, // ##
0x01, 0x80, // ##
0x1F, 0x80, // ######
0x31, 0x80, // ## ##
0x33, 0x80, // ## ###
0x1D, 0xC0, // ### ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2112 'b' (11 pixels wide)
0x00, 0x00, //
0x70, 0x00, // ###
0x30, 0x00, // ##
0x30, 0x00, // ##
0x37, 0x00, // ## ###
0x39, 0x80, // ### ##
0x30, 0xC0, // ## ##
0x30, 0xC0, // ## ##
0x30, 0xC0, // ## ##
0x39, 0x80, // ### ##
0x77, 0x00, // ### ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2144 'c' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x1E, 0x80, // #### #
0x31, 0x80, // ## ##
0x60, 0x80, // ## #
0x60, 0x00, // ##
0x60, 0x80, // ## #
0x31, 0x80, // ## ##
0x1F, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2176 'd' (11 pixels wide)
0x00, 0x00, //
0x03, 0x80, // ###
0x01, 0x80, // ##
0x01, 0x80, // ##
0x1D, 0x80, // ### ##
0x33, 0x80, // ## ###
0x61, 0x80, // ## ##
0x61, 0x80, // ## ##
0x61, 0x80, // ## ##
0x33, 0x80, // ## ###
0x1D, 0xC0, // ### ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2208 'e' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0x00, // #####
0x31, 0x80, // ## ##
0x60, 0xC0, // ## ##
0x7F, 0xC0, // #########
0x60, 0x00, // ##
0x30, 0xC0, // ## ##
0x1F, 0x80, // ######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2240 'f' (11 pixels wide)
0x00, 0x00, //
0x07, 0xE0, // ######
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x3F, 0x80, // #######
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x3F, 0x80, // #######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2272 'g' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x1D, 0xC0, // ### ###
0x33, 0x80, // ## ###
0x61, 0x80, // ## ##
0x61, 0x80, // ## ##
0x61, 0x80, // ## ##
0x33, 0x80, // ## ###
0x1D, 0x80, // ### ##
0x01, 0x80, // ##
0x01, 0x80, // ##
0x1F, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
// @2304 'h' (11 pixels wide)
0x00, 0x00, //
0x70, 0x00, // ###
0x30, 0x00, // ##
0x30, 0x00, // ##
0x37, 0x00, // ## ###
0x39, 0x80, // ### ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x7B, 0xC0, // #### ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2336 'i' (11 pixels wide)
0x00, 0x00, //
0x06, 0x00, // ##
0x06, 0x00, // ##
0x00, 0x00, //
0x1E, 0x00, // ####
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x3F, 0xC0, // ########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2368 'j' (11 pixels wide)
0x00, 0x00, //
0x06, 0x00, // ##
0x06, 0x00, // ##
0x00, 0x00, //
0x3F, 0x00, // ######
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x3E, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
// @2400 'k' (11 pixels wide)
0x00, 0x00, //
0x70, 0x00, // ###
0x30, 0x00, // ##
0x30, 0x00, // ##
0x37, 0x80, // ## ####
0x36, 0x00, // ## ##
0x3C, 0x00, // ####
0x3C, 0x00, // ####
0x36, 0x00, // ## ##
0x33, 0x00, // ## ##
0x77, 0xC0, // ### #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2432 'l' (11 pixels wide)
0x00, 0x00, //
0x1E, 0x00, // ####
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x3F, 0xC0, // ########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2464 'm' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x7F, 0x80, // ########
0x36, 0xC0, // ## ## ##
0x36, 0xC0, // ## ## ##
0x36, 0xC0, // ## ## ##
0x36, 0xC0, // ## ## ##
0x36, 0xC0, // ## ## ##
0x76, 0xE0, // ### ## ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2496 'n' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x77, 0x00, // ### ###
0x39, 0x80, // ### ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x7B, 0xC0, // #### ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2528 'o' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0x00, // #####
0x31, 0x80, // ## ##
0x60, 0xC0, // ## ##
0x60, 0xC0, // ## ##
0x60, 0xC0, // ## ##
0x31, 0x80, // ## ##
0x1F, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2560 'p' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x77, 0x00, // ### ###
0x39, 0x80, // ### ##
0x30, 0xC0, // ## ##
0x30, 0xC0, // ## ##
0x30, 0xC0, // ## ##
0x39, 0x80, // ### ##
0x37, 0x00, // ## ###
0x30, 0x00, // ##
0x30, 0x00, // ##
0x7C, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
// @2592 'q' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x1D, 0xC0, // ### ###
0x33, 0x80, // ## ###
0x61, 0x80, // ## ##
0x61, 0x80, // ## ##
0x61, 0x80, // ## ##
0x33, 0x80, // ## ###
0x1D, 0x80, // ### ##
0x01, 0x80, // ##
0x01, 0x80, // ##
0x07, 0xC0, // #####
0x00, 0x00, //
0x00, 0x00, //
// @2624 'r' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x7B, 0x80, // #### ###
0x1C, 0xC0, // ### ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0x7F, 0x00, // #######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2656 's' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0x80, // ######
0x31, 0x80, // ## ##
0x3C, 0x00, // ####
0x1F, 0x00, // #####
0x03, 0x80, // ###
0x31, 0x80, // ## ##
0x3F, 0x00, // ######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2688 't' (11 pixels wide)
0x00, 0x00, //
0x18, 0x00, // ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0x7F, 0x00, // #######
0x18, 0x00, // ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0x18, 0x80, // ## #
0x0F, 0x00, // ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2720 'u' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x73, 0x80, // ### ###
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x33, 0x80, // ## ###
0x1D, 0xC0, // ### ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2752 'v' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x7B, 0xC0, // #### ####
0x31, 0x80, // ## ##
0x31, 0x80, // ## ##
0x1B, 0x00, // ## ##
0x1B, 0x00, // ## ##
0x0E, 0x00, // ###
0x0E, 0x00, // ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2784 'w' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0xF1, 0xE0, // #### ####
0x60, 0xC0, // ## ##
0x64, 0xC0, // ## # ##
0x6E, 0xC0, // ## ### ##
0x3B, 0x80, // ### ###
0x3B, 0x80, // ### ###
0x31, 0x80, // ## ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2816 'x' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x7B, 0xC0, // #### ####
0x1B, 0x00, // ## ##
0x0E, 0x00, // ###
0x0E, 0x00, // ###
0x0E, 0x00, // ###
0x1B, 0x00, // ## ##
0x7B, 0xC0, // #### ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2848 'y' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x79, 0xE0, // #### ####
0x30, 0xC0, // ## ##
0x19, 0x80, // ## ##
0x19, 0x80, // ## ##
0x0B, 0x00, // # ##
0x0F, 0x00, // ####
0x06, 0x00, // ##
0x06, 0x00, // ##
0x0C, 0x00, // ##
0x3E, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
// @2880 'z' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x3F, 0x80, // #######
0x21, 0x80, // # ##
0x03, 0x00, // ##
0x0E, 0x00, // ###
0x18, 0x00, // ##
0x30, 0x80, // ## #
0x3F, 0x80, // #######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2912 '{' (11 pixels wide)
0x00, 0x00, //
0x06, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x18, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x06, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2944 '|' (11 pixels wide)
0x00, 0x00, //
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2976 '}' (11 pixels wide)
0x00, 0x00, //
0x0C, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x03, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x0C, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3008 '~' (11 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x18, 0x00, // ##
0x24, 0x80, // # # #
0x03, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
};
sFONT Font16 = {
Font16_Table,
11, /* Width */
16, /* Height */
};
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
/**
******************************************************************************
* @file font20.c
* @author MCD Application Team
* @version V1.0.0
* @date 18-February-2014
* @brief This file provides text font20 for STM32xx-EVAL's LCD driver.
******************************************************************************
* @attention
*
* <h2><center>&copy; COPYRIGHT(c) 2014 STMicroelectronics</center></h2>
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of STMicroelectronics nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "fonts.h"
// Character bitmaps for Courier New 15pt
const uint8_t Font20_Table[] =
{
// @0 ' ' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @40 '!' (14 pixels wide)
0x00, 0x00, //
0x07, 0x00, // ###
0x07, 0x00, // ###
0x07, 0x00, // ###
0x07, 0x00, // ###
0x07, 0x00, // ###
0x07, 0x00, // ###
0x07, 0x00, // ###
0x02, 0x00, // #
0x02, 0x00, // #
0x00, 0x00, //
0x00, 0x00, //
0x07, 0x00, // ###
0x07, 0x00, // ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @80 '"' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x1C, 0xE0, // ### ###
0x1C, 0xE0, // ### ###
0x1C, 0xE0, // ### ###
0x08, 0x40, // # #
0x08, 0x40, // # #
0x08, 0x40, // # #
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @120 '#' (14 pixels wide)
0x0C, 0xC0, // ## ##
0x0C, 0xC0, // ## ##
0x0C, 0xC0, // ## ##
0x0C, 0xC0, // ## ##
0x0C, 0xC0, // ## ##
0x3F, 0xF0, // ##########
0x3F, 0xF0, // ##########
0x0C, 0xC0, // ## ##
0x0C, 0xC0, // ## ##
0x3F, 0xF0, // ##########
0x3F, 0xF0, // ##########
0x0C, 0xC0, // ## ##
0x0C, 0xC0, // ## ##
0x0C, 0xC0, // ## ##
0x0C, 0xC0, // ## ##
0x0C, 0xC0, // ## ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @160 '$' (14 pixels wide)
0x03, 0x00, // ##
0x03, 0x00, // ##
0x07, 0xE0, // ######
0x0F, 0xE0, // #######
0x18, 0x60, // ## ##
0x18, 0x00, // ##
0x1F, 0x00, // #####
0x0F, 0xC0, // ######
0x00, 0xE0, // ###
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x1F, 0xC0, // #######
0x1F, 0x80, // ######
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @200 '%' (14 pixels wide)
0x00, 0x00, //
0x1C, 0x00, // ###
0x22, 0x00, // # #
0x22, 0x00, // # #
0x22, 0x00, // # #
0x1C, 0x60, // ### ##
0x01, 0xE0, // ####
0x0F, 0x80, // #####
0x3C, 0x00, // ####
0x31, 0xC0, // ## ###
0x02, 0x20, // # #
0x02, 0x20, // # #
0x02, 0x20, // # #
0x01, 0xC0, // ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @240 '&' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x03, 0xE0, // #####
0x0F, 0xE0, // #######
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x06, 0x00, // ##
0x0F, 0x30, // #### ##
0x1F, 0xF0, // #########
0x19, 0xE0, // ## ####
0x18, 0xC0, // ## ##
0x1F, 0xF0, // #########
0x07, 0xB0, // #### ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @280 ''' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x03, 0x80, // ###
0x03, 0x80, // ###
0x03, 0x80, // ###
0x01, 0x00, // #
0x01, 0x00, // #
0x01, 0x00, // #
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @320 '(' (14 pixels wide)
0x00, 0x00, //
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x01, 0x80, // ##
0x01, 0x80, // ##
0x01, 0x80, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x01, 0x80, // ##
0x01, 0x80, // ##
0x01, 0x80, // ##
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @360 ')' (14 pixels wide)
0x00, 0x00, //
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @400 '*' (14 pixels wide)
0x00, 0x00, //
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x1B, 0x60, // ## ## ##
0x1F, 0xE0, // ########
0x07, 0x80, // ####
0x07, 0x80, // ####
0x0F, 0xC0, // ######
0x0C, 0xC0, // ## ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @440 '+' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x3F, 0xF0, // ##########
0x3F, 0xF0, // ##########
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @480 ',' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x03, 0x80, // ###
0x03, 0x00, // ##
0x03, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x04, 0x00, // #
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @520 '-' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x3F, 0xE0, // #########
0x3F, 0xE0, // #########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @560 '.' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x03, 0x80, // ###
0x03, 0x80, // ###
0x03, 0x80, // ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @600 '/' (14 pixels wide)
0x00, 0x60, // ##
0x00, 0x60, // ##
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x01, 0x80, // ##
0x01, 0x80, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @640 '0' (14 pixels wide)
0x00, 0x00, //
0x0F, 0x80, // #####
0x1F, 0xC0, // #######
0x18, 0xC0, // ## ##
0x30, 0x60, // ## ##
0x30, 0x60, // ## ##
0x30, 0x60, // ## ##
0x30, 0x60, // ## ##
0x30, 0x60, // ## ##
0x30, 0x60, // ## ##
0x30, 0x60, // ## ##
0x18, 0xC0, // ## ##
0x1F, 0xC0, // #######
0x0F, 0x80, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @680 '1' (14 pixels wide)
0x00, 0x00, //
0x03, 0x00, // ##
0x1F, 0x00, // #####
0x1F, 0x00, // #####
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x1F, 0xE0, // ########
0x1F, 0xE0, // ########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @720 '2' (14 pixels wide)
0x00, 0x00, //
0x0F, 0x80, // #####
0x1F, 0xC0, // #######
0x38, 0xE0, // ### ###
0x30, 0x60, // ## ##
0x00, 0x60, // ##
0x00, 0xC0, // ##
0x01, 0x80, // ##
0x03, 0x00, // ##
0x06, 0x00, // ##
0x0C, 0x00, // ##
0x18, 0x00, // ##
0x3F, 0xE0, // #########
0x3F, 0xE0, // #########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @760 '3' (14 pixels wide)
0x00, 0x00, //
0x0F, 0x80, // #####
0x3F, 0xC0, // ########
0x30, 0xE0, // ## ###
0x00, 0x60, // ##
0x00, 0xE0, // ###
0x07, 0xC0, // #####
0x07, 0xC0, // #####
0x00, 0xE0, // ###
0x00, 0x60, // ##
0x00, 0x60, // ##
0x60, 0xE0, // ## ###
0x7F, 0xC0, // #########
0x3F, 0x80, // #######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @800 '4' (14 pixels wide)
0x00, 0x00, //
0x01, 0xC0, // ###
0x03, 0xC0, // ####
0x03, 0xC0, // ####
0x06, 0xC0, // ## ##
0x0C, 0xC0, // ## ##
0x0C, 0xC0, // ## ##
0x18, 0xC0, // ## ##
0x30, 0xC0, // ## ##
0x3F, 0xE0, // #########
0x3F, 0xE0, // #########
0x00, 0xC0, // ##
0x03, 0xE0, // #####
0x03, 0xE0, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @840 '5' (14 pixels wide)
0x00, 0x00, //
0x1F, 0xC0, // #######
0x1F, 0xC0, // #######
0x18, 0x00, // ##
0x18, 0x00, // ##
0x1F, 0x80, // ######
0x1F, 0xC0, // #######
0x18, 0xE0, // ## ###
0x00, 0x60, // ##
0x00, 0x60, // ##
0x00, 0x60, // ##
0x30, 0xE0, // ## ###
0x3F, 0xC0, // ########
0x1F, 0x80, // ######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @880 '6' (14 pixels wide)
0x00, 0x00, //
0x03, 0xE0, // #####
0x0F, 0xE0, // #######
0x1E, 0x00, // ####
0x18, 0x00, // ##
0x38, 0x00, // ###
0x37, 0x80, // ## ####
0x3F, 0xC0, // ########
0x38, 0xE0, // ### ###
0x30, 0x60, // ## ##
0x30, 0x60, // ## ##
0x18, 0xE0, // ## ###
0x1F, 0xC0, // #######
0x07, 0x80, // ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @920 '7' (14 pixels wide)
0x00, 0x00, //
0x3F, 0xE0, // #########
0x3F, 0xE0, // #########
0x30, 0x60, // ## ##
0x00, 0x60, // ##
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x01, 0x80, // ##
0x01, 0x80, // ##
0x01, 0x80, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @960 '8' (14 pixels wide)
0x00, 0x00, //
0x0F, 0x80, // #####
0x1F, 0xC0, // #######
0x38, 0xE0, // ### ###
0x30, 0x60, // ## ##
0x38, 0xE0, // ### ###
0x1F, 0xC0, // #######
0x1F, 0xC0, // #######
0x38, 0xE0, // ### ###
0x30, 0x60, // ## ##
0x30, 0x60, // ## ##
0x38, 0xE0, // ### ###
0x1F, 0xC0, // #######
0x0F, 0x80, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1000 '9' (14 pixels wide)
0x00, 0x00, //
0x0F, 0x00, // ####
0x1F, 0xC0, // #######
0x38, 0xC0, // ### ##
0x30, 0x60, // ## ##
0x30, 0x60, // ## ##
0x38, 0xE0, // ### ###
0x1F, 0xE0, // ########
0x0F, 0x60, // #### ##
0x00, 0xE0, // ###
0x00, 0xC0, // ##
0x03, 0xC0, // ####
0x3F, 0x80, // #######
0x3E, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1040 ':' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x03, 0x80, // ###
0x03, 0x80, // ###
0x03, 0x80, // ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x03, 0x80, // ###
0x03, 0x80, // ###
0x03, 0x80, // ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1080 ';' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x01, 0xC0, // ###
0x01, 0xC0, // ###
0x01, 0xC0, // ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x03, 0x80, // ###
0x03, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x04, 0x00, // #
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1120 '<' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x30, // ##
0x00, 0xF0, // ####
0x03, 0xC0, // ####
0x07, 0x00, // ###
0x1C, 0x00, // ###
0x78, 0x00, // ####
0x1C, 0x00, // ###
0x07, 0x00, // ###
0x03, 0xC0, // ####
0x00, 0xF0, // ####
0x00, 0x30, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1160 '=' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x7F, 0xF0, // ###########
0x7F, 0xF0, // ###########
0x00, 0x00, //
0x00, 0x00, //
0x7F, 0xF0, // ###########
0x7F, 0xF0, // ###########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1200 '>' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x30, 0x00, // ##
0x3C, 0x00, // ####
0x0F, 0x00, // ####
0x03, 0x80, // ###
0x00, 0xE0, // ###
0x00, 0x78, // ####
0x00, 0xE0, // ###
0x03, 0x80, // ###
0x0F, 0x00, // ####
0x3C, 0x00, // ####
0x30, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1240 '?' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x0F, 0x80, // #####
0x1F, 0xC0, // #######
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x00, 0x60, // ##
0x01, 0xC0, // ###
0x03, 0x80, // ###
0x03, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x07, 0x00, // ###
0x07, 0x00, // ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1280 '@' (14 pixels wide)
0x00, 0x00, //
0x03, 0x80, // ###
0x0C, 0x80, // ## #
0x08, 0x40, // # #
0x10, 0x40, // # #
0x10, 0x40, // # #
0x11, 0xC0, // # ###
0x12, 0x40, // # # #
0x12, 0x40, // # # #
0x12, 0x40, // # # #
0x11, 0xC0, // # ###
0x10, 0x00, // #
0x08, 0x00, // #
0x08, 0x40, // # #
0x07, 0x80, // ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1320 'A' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0x80, // ######
0x1F, 0x80, // ######
0x03, 0x80, // ###
0x06, 0xC0, // ## ##
0x06, 0xC0, // ## ##
0x0C, 0xC0, // ## ##
0x0C, 0x60, // ## ##
0x1F, 0xE0, // ########
0x1F, 0xE0, // ########
0x30, 0x30, // ## ##
0x78, 0x78, // #### ####
0x78, 0x78, // #### ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1360 'B' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x3F, 0x80, // #######
0x3F, 0xC0, // ########
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x18, 0xE0, // ## ###
0x1F, 0xC0, // #######
0x1F, 0xE0, // ########
0x18, 0x70, // ## ###
0x18, 0x30, // ## ##
0x18, 0x30, // ## ##
0x3F, 0xF0, // ##########
0x3F, 0xE0, // #########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1400 'C' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x07, 0xB0, // #### ##
0x0F, 0xF0, // ########
0x1C, 0x70, // ### ###
0x38, 0x30, // ### ##
0x30, 0x00, // ##
0x30, 0x00, // ##
0x30, 0x00, // ##
0x30, 0x00, // ##
0x38, 0x30, // ### ##
0x1C, 0x70, // ### ###
0x0F, 0xE0, // #######
0x07, 0xC0, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1440 'D' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x7F, 0x80, // ########
0x7F, 0xC0, // #########
0x30, 0xE0, // ## ###
0x30, 0x70, // ## ###
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x30, 0x70, // ## ###
0x30, 0xE0, // ## ###
0x7F, 0xC0, // #########
0x7F, 0x80, // ########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1480 'E' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x3F, 0xF0, // ##########
0x3F, 0xF0, // ##########
0x18, 0x30, // ## ##
0x18, 0x30, // ## ##
0x19, 0x80, // ## ##
0x1F, 0x80, // ######
0x1F, 0x80, // ######
0x19, 0x80, // ## ##
0x18, 0x30, // ## ##
0x18, 0x30, // ## ##
0x3F, 0xF0, // ##########
0x3F, 0xF0, // ##########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1520 'F' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x3F, 0xF0, // ##########
0x3F, 0xF0, // ##########
0x18, 0x30, // ## ##
0x18, 0x30, // ## ##
0x19, 0x80, // ## ##
0x1F, 0x80, // ######
0x1F, 0x80, // ######
0x19, 0x80, // ## ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0x3F, 0x00, // ######
0x3F, 0x00, // ######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1560 'G' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x07, 0xB0, // #### ##
0x1F, 0xF0, // #########
0x18, 0x70, // ## ###
0x30, 0x30, // ## ##
0x30, 0x00, // ##
0x30, 0x00, // ##
0x31, 0xF8, // ## ######
0x31, 0xF8, // ## ######
0x30, 0x30, // ## ##
0x18, 0x30, // ## ##
0x1F, 0xF0, // #########
0x07, 0xC0, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1600 'H' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x3C, 0xF0, // #### ####
0x3C, 0xF0, // #### ####
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x1F, 0xE0, // ########
0x1F, 0xE0, // ########
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x3C, 0xF0, // #### ####
0x3C, 0xF0, // #### ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1640 'I' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0xE0, // ########
0x1F, 0xE0, // ########
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x1F, 0xE0, // ########
0x1F, 0xE0, // ########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1680 'J' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x03, 0xF8, // #######
0x03, 0xF8, // #######
0x00, 0x60, // ##
0x00, 0x60, // ##
0x00, 0x60, // ##
0x00, 0x60, // ##
0x30, 0x60, // ## ##
0x30, 0x60, // ## ##
0x30, 0x60, // ## ##
0x30, 0xE0, // ## ###
0x3F, 0xC0, // ########
0x0F, 0x80, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1720 'K' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x3E, 0xF8, // ##### #####
0x3E, 0xF8, // ##### #####
0x18, 0xE0, // ## ###
0x19, 0x80, // ## ##
0x1B, 0x00, // ## ##
0x1F, 0x00, // #####
0x1D, 0x80, // ### ##
0x18, 0xC0, // ## ##
0x18, 0xC0, // ## ##
0x18, 0x60, // ## ##
0x3E, 0x78, // ##### ####
0x3E, 0x38, // ##### ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1760 'L' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x3F, 0x00, // ######
0x3F, 0x00, // ######
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x30, // ## ##
0x0C, 0x30, // ## ##
0x0C, 0x30, // ## ##
0x3F, 0xF0, // ##########
0x3F, 0xF0, // ##########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1800 'M' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x78, 0x78, // #### ####
0x78, 0x78, // #### ####
0x38, 0x70, // ### ###
0x3C, 0xF0, // #### ####
0x34, 0xB0, // ## # # ##
0x37, 0xB0, // ## #### ##
0x37, 0xB0, // ## #### ##
0x33, 0x30, // ## ## ##
0x33, 0x30, // ## ## ##
0x30, 0x30, // ## ##
0x7C, 0xF8, // ##### #####
0x7C, 0xF8, // ##### #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1840 'N' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x39, 0xF0, // ### #####
0x3D, 0xF0, // #### #####
0x1C, 0x60, // ### ##
0x1E, 0x60, // #### ##
0x1E, 0x60, // #### ##
0x1B, 0x60, // ## ## ##
0x1B, 0x60, // ## ## ##
0x19, 0xE0, // ## ####
0x19, 0xE0, // ## ####
0x18, 0xE0, // ## ###
0x3E, 0xE0, // ##### ###
0x3E, 0x60, // ##### ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1880 'O' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x07, 0x80, // ####
0x0F, 0xC0, // ######
0x1C, 0xE0, // ### ###
0x38, 0x70, // ### ###
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x38, 0x70, // ### ###
0x1C, 0xE0, // ### ###
0x0F, 0xC0, // ######
0x07, 0x80, // ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1920 'P' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x3F, 0xC0, // ########
0x3F, 0xE0, // #########
0x18, 0x70, // ## ###
0x18, 0x30, // ## ##
0x18, 0x30, // ## ##
0x18, 0x70, // ## ###
0x1F, 0xE0, // ########
0x1F, 0xC0, // #######
0x18, 0x00, // ##
0x18, 0x00, // ##
0x3F, 0x00, // ######
0x3F, 0x00, // ######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @1960 'Q' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x07, 0x80, // ####
0x0F, 0xC0, // ######
0x1C, 0xE0, // ### ###
0x38, 0x70, // ### ###
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x38, 0x70, // ### ###
0x1C, 0xE0, // ### ###
0x0F, 0xC0, // ######
0x07, 0x80, // ####
0x07, 0xB0, // #### ##
0x0F, 0xF0, // ########
0x0C, 0xE0, // ## ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2000 'R' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x3F, 0xC0, // ########
0x3F, 0xE0, // #########
0x18, 0x70, // ## ###
0x18, 0x30, // ## ##
0x18, 0x70, // ## ###
0x1F, 0xE0, // ########
0x1F, 0xC0, // #######
0x18, 0xE0, // ## ###
0x18, 0x60, // ## ##
0x18, 0x70, // ## ###
0x3E, 0x38, // ##### ###
0x3E, 0x18, // ##### ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2040 'S' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x0F, 0xB0, // ##### ##
0x1F, 0xF0, // #########
0x38, 0x70, // ### ###
0x30, 0x30, // ## ##
0x38, 0x00, // ###
0x1F, 0x80, // ######
0x07, 0xE0, // ######
0x00, 0x70, // ###
0x30, 0x30, // ## ##
0x38, 0x70, // ### ###
0x3F, 0xE0, // #########
0x37, 0xC0, // ## #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2080 'T' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x3F, 0xF0, // ##########
0x3F, 0xF0, // ##########
0x33, 0x30, // ## ## ##
0x33, 0x30, // ## ## ##
0x33, 0x30, // ## ## ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x0F, 0xC0, // ######
0x0F, 0xC0, // ######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2120 'U' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x3C, 0xF0, // #### ####
0x3C, 0xF0, // #### ####
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x1C, 0xE0, // ### ###
0x0F, 0xC0, // ######
0x07, 0x80, // ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2160 'V' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x78, 0xF0, // #### ####
0x78, 0xF0, // #### ####
0x30, 0x60, // ## ##
0x30, 0x60, // ## ##
0x18, 0xC0, // ## ##
0x18, 0xC0, // ## ##
0x0D, 0x80, // ## ##
0x0D, 0x80, // ## ##
0x0D, 0x80, // ## ##
0x07, 0x00, // ###
0x07, 0x00, // ###
0x07, 0x00, // ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2200 'W' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x7C, 0x7C, // ##### #####
0x7C, 0x7C, // ##### #####
0x30, 0x18, // ## ##
0x33, 0x98, // ## ### ##
0x33, 0x98, // ## ### ##
0x33, 0x98, // ## ### ##
0x36, 0xD8, // ## ## ## ##
0x16, 0xD0, // # ## ## #
0x1C, 0x70, // ### ###
0x1C, 0x70, // ### ###
0x1C, 0x70, // ### ###
0x18, 0x30, // ## ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2240 'X' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x78, 0xF0, // #### ####
0x78, 0xF0, // #### ####
0x30, 0x60, // ## ##
0x18, 0xC0, // ## ##
0x0D, 0x80, // ## ##
0x07, 0x00, // ###
0x07, 0x00, // ###
0x0D, 0x80, // ## ##
0x18, 0xC0, // ## ##
0x30, 0x60, // ## ##
0x78, 0xF0, // #### ####
0x78, 0xF0, // #### ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2280 'Y' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x3C, 0xF0, // #### ####
0x3C, 0xF0, // #### ####
0x18, 0x60, // ## ##
0x0C, 0xC0, // ## ##
0x07, 0x80, // ####
0x07, 0x80, // ####
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x0F, 0xC0, // ######
0x0F, 0xC0, // ######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2320 'Z' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0xE0, // ########
0x1F, 0xE0, // ########
0x18, 0x60, // ## ##
0x18, 0xC0, // ## ##
0x01, 0x80, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x06, 0x00, // ##
0x0C, 0x60, // ## ##
0x18, 0x60, // ## ##
0x1F, 0xE0, // ########
0x1F, 0xE0, // ########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2360 '[' (14 pixels wide)
0x00, 0x00, //
0x03, 0xC0, // ####
0x03, 0xC0, // ####
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0xC0, // ####
0x03, 0xC0, // ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2400 '\' (14 pixels wide)
0x18, 0x00, // ##
0x18, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x01, 0x80, // ##
0x01, 0x80, // ##
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x00, 0x60, // ##
0x00, 0x60, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2440 ']' (14 pixels wide)
0x00, 0x00, //
0x0F, 0x00, // ####
0x0F, 0x00, // ####
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x0F, 0x00, // ####
0x0F, 0x00, // ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2480 '^' (14 pixels wide)
0x00, 0x00, //
0x02, 0x00, // #
0x07, 0x00, // ###
0x0D, 0x80, // ## ##
0x18, 0xC0, // ## ##
0x30, 0x60, // ## ##
0x20, 0x20, // # #
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2520 '_' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0xFF, 0xFC, // ##############
0xFF, 0xFC, // ##############
// @2560 '`' (14 pixels wide)
0x00, 0x00, //
0x04, 0x00, // #
0x03, 0x00, // ##
0x00, 0x80, // #
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2600 'a' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x0F, 0xC0, // ######
0x1F, 0xE0, // ########
0x00, 0x60, // ##
0x0F, 0xE0, // #######
0x1F, 0xE0, // ########
0x38, 0x60, // ### ##
0x30, 0xE0, // ## ###
0x3F, 0xF0, // ##########
0x1F, 0x70, // ##### ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2640 'b' (14 pixels wide)
0x00, 0x00, //
0x70, 0x00, // ###
0x70, 0x00, // ###
0x30, 0x00, // ##
0x30, 0x00, // ##
0x37, 0x80, // ## ####
0x3F, 0xE0, // #########
0x38, 0x60, // ### ##
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x38, 0x60, // ### ##
0x7F, 0xE0, // ##########
0x77, 0x80, // ### ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2680 'c' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x07, 0xB0, // #### ##
0x1F, 0xF0, // #########
0x18, 0x30, // ## ##
0x30, 0x30, // ## ##
0x30, 0x00, // ##
0x30, 0x00, // ##
0x38, 0x30, // ### ##
0x1F, 0xF0, // #########
0x0F, 0xC0, // ######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2720 'd' (14 pixels wide)
0x00, 0x00, //
0x00, 0x70, // ###
0x00, 0x70, // ###
0x00, 0x30, // ##
0x00, 0x30, // ##
0x07, 0xB0, // #### ##
0x1F, 0xF0, // #########
0x18, 0x70, // ## ###
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x38, 0x70, // ### ###
0x1F, 0xF8, // ##########
0x07, 0xB8, // #### ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2760 'e' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x07, 0x80, // ####
0x1F, 0xE0, // ########
0x18, 0x60, // ## ##
0x3F, 0xF0, // ##########
0x3F, 0xF0, // ##########
0x30, 0x00, // ##
0x18, 0x30, // ## ##
0x1F, 0xF0, // #########
0x07, 0xC0, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2800 'f' (14 pixels wide)
0x00, 0x00, //
0x03, 0xF0, // ######
0x07, 0xF0, // #######
0x06, 0x00, // ##
0x06, 0x00, // ##
0x1F, 0xE0, // ########
0x1F, 0xE0, // ########
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x1F, 0xE0, // ########
0x1F, 0xE0, // ########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2840 'g' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x07, 0xB8, // #### ###
0x1F, 0xF8, // ##########
0x18, 0x70, // ## ###
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x18, 0x70, // ## ###
0x1F, 0xF0, // #########
0x07, 0xB0, // #### ##
0x00, 0x30, // ##
0x00, 0x70, // ###
0x0F, 0xE0, // #######
0x0F, 0xC0, // ######
0x00, 0x00, //
0x00, 0x00, //
// @2880 'h' (14 pixels wide)
0x00, 0x00, //
0x38, 0x00, // ###
0x38, 0x00, // ###
0x18, 0x00, // ##
0x18, 0x00, // ##
0x1B, 0xC0, // ## ####
0x1F, 0xE0, // ########
0x1C, 0x60, // ### ##
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x3C, 0xF0, // #### ####
0x3C, 0xF0, // #### ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2920 'i' (14 pixels wide)
0x00, 0x00, //
0x03, 0x00, // ##
0x03, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0x00, // #####
0x1F, 0x00, // #####
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x1F, 0xE0, // ########
0x1F, 0xE0, // ########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @2960 'j' (14 pixels wide)
0x00, 0x00, //
0x03, 0x00, // ##
0x03, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0xC0, // #######
0x1F, 0xC0, // #######
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x00, 0xC0, // ##
0x01, 0xC0, // ###
0x3F, 0x80, // #######
0x3F, 0x00, // ######
0x00, 0x00, //
0x00, 0x00, //
// @3000 'k' (14 pixels wide)
0x00, 0x00, //
0x38, 0x00, // ###
0x38, 0x00, // ###
0x18, 0x00, // ##
0x18, 0x00, // ##
0x1B, 0xE0, // ## #####
0x1B, 0xE0, // ## #####
0x1B, 0x00, // ## ##
0x1E, 0x00, // ####
0x1E, 0x00, // ####
0x1B, 0x00, // ## ##
0x19, 0x80, // ## ##
0x39, 0xF0, // ### #####
0x39, 0xF0, // ### #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3040 'l' (14 pixels wide)
0x00, 0x00, //
0x1F, 0x00, // #####
0x1F, 0x00, // #####
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x1F, 0xE0, // ########
0x1F, 0xE0, // ########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3080 'm' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x7E, 0xE0, // ###### ###
0x7F, 0xF0, // ###########
0x33, 0x30, // ## ## ##
0x33, 0x30, // ## ## ##
0x33, 0x30, // ## ## ##
0x33, 0x30, // ## ## ##
0x33, 0x30, // ## ## ##
0x7B, 0xB8, // #### ### ###
0x7B, 0xB8, // #### ### ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3120 'n' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x3B, 0xC0, // ### ####
0x3F, 0xE0, // #########
0x1C, 0x60, // ### ##
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x3C, 0xF0, // #### ####
0x3C, 0xF0, // #### ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3160 'o' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x07, 0x80, // ####
0x1F, 0xE0, // ########
0x18, 0x60, // ## ##
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x18, 0x60, // ## ##
0x1F, 0xE0, // ########
0x07, 0x80, // ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3200 'p' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x77, 0x80, // ### ####
0x7F, 0xE0, // ##########
0x38, 0x60, // ### ##
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x38, 0x60, // ### ##
0x3F, 0xE0, // #########
0x37, 0x80, // ## ####
0x30, 0x00, // ##
0x30, 0x00, // ##
0x7C, 0x00, // #####
0x7C, 0x00, // #####
0x00, 0x00, //
0x00, 0x00, //
// @3240 'q' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x07, 0xB8, // #### ###
0x1F, 0xF8, // ##########
0x18, 0x70, // ## ###
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x30, 0x30, // ## ##
0x18, 0x70, // ## ###
0x1F, 0xF0, // #########
0x07, 0xB0, // #### ##
0x00, 0x30, // ##
0x00, 0x30, // ##
0x00, 0xF8, // #####
0x00, 0xF8, // #####
0x00, 0x00, //
0x00, 0x00, //
// @3280 'r' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x3C, 0xE0, // #### ###
0x3D, 0xF0, // #### #####
0x0F, 0x30, // #### ##
0x0E, 0x00, // ###
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x3F, 0xC0, // ########
0x3F, 0xC0, // ########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3320 's' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x07, 0xE0, // ######
0x1F, 0xE0, // ########
0x18, 0x60, // ## ##
0x1E, 0x00, // ####
0x0F, 0xC0, // ######
0x01, 0xE0, // ####
0x18, 0x60, // ## ##
0x1F, 0xE0, // ########
0x1F, 0x80, // ######
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3360 't' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x3F, 0xE0, // #########
0x3F, 0xE0, // #########
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x00, // ##
0x0C, 0x30, // ## ##
0x0F, 0xF0, // ########
0x07, 0xC0, // #####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3400 'u' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x38, 0xE0, // ### ###
0x38, 0xE0, // ### ###
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x18, 0x60, // ## ##
0x18, 0xE0, // ## ###
0x1F, 0xF0, // #########
0x0F, 0x70, // #### ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3440 'v' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x78, 0xF0, // #### ####
0x78, 0xF0, // #### ####
0x30, 0x60, // ## ##
0x18, 0xC0, // ## ##
0x18, 0xC0, // ## ##
0x0D, 0x80, // ## ##
0x0D, 0x80, // ## ##
0x07, 0x00, // ###
0x07, 0x00, // ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3480 'w' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x78, 0xF0, // #### ####
0x78, 0xF0, // #### ####
0x32, 0x60, // ## # ##
0x32, 0x60, // ## # ##
0x37, 0xE0, // ## ######
0x1D, 0xC0, // ### ###
0x1D, 0xC0, // ### ###
0x18, 0xC0, // ## ##
0x18, 0xC0, // ## ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3520 'x' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x3C, 0xF0, // #### ####
0x3C, 0xF0, // #### ####
0x0C, 0xC0, // ## ##
0x07, 0x80, // ####
0x03, 0x00, // ##
0x07, 0x80, // ####
0x0C, 0xC0, // ## ##
0x3C, 0xF0, // #### ####
0x3C, 0xF0, // #### ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3560 'y' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x78, 0xF0, // #### ####
0x78, 0xF0, // #### ####
0x30, 0x60, // ## ##
0x18, 0xC0, // ## ##
0x18, 0xC0, // ## ##
0x0D, 0x80, // ## ##
0x0F, 0x80, // #####
0x07, 0x00, // ###
0x06, 0x00, // ##
0x06, 0x00, // ##
0x0C, 0x00, // ##
0x7F, 0x00, // #######
0x7F, 0x00, // #######
0x00, 0x00, //
0x00, 0x00, //
// @3600 'z' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x1F, 0xE0, // ########
0x1F, 0xE0, // ########
0x18, 0xC0, // ## ##
0x01, 0x80, // ##
0x03, 0x00, // ##
0x06, 0x00, // ##
0x0C, 0x60, // ## ##
0x1F, 0xE0, // ########
0x1F, 0xE0, // ########
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3640 '{' (14 pixels wide)
0x00, 0x00, //
0x01, 0xC0, // ###
0x03, 0xC0, // ####
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x07, 0x00, // ###
0x0E, 0x00, // ###
0x07, 0x00, // ###
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0xC0, // ####
0x01, 0xC0, // ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3680 '|' (14 pixels wide)
0x00, 0x00, //
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x03, 0x00, // ##
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3720 '}' (14 pixels wide)
0x00, 0x00, //
0x1C, 0x00, // ###
0x1E, 0x00, // ####
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x07, 0x00, // ###
0x03, 0x80, // ###
0x07, 0x00, // ###
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x06, 0x00, // ##
0x1E, 0x00, // ####
0x1C, 0x00, // ###
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
// @3760 '~' (14 pixels wide)
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x0E, 0x00, // ###
0x3F, 0x30, // ###### ##
0x33, 0xF0, // ## ######
0x01, 0xE0, // ####
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
0x00, 0x00, //
};
sFONT Font20 = {
Font20_Table,
14, /* Width */
20, /* Height */
};
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
/**
******************************************************************************
* @file font24.c
* @author MCD Application Team
* @version V1.0.0
* @date 18-February-2014
* @brief This file provides text font24 for STM32xx-EVAL's LCD driver.
******************************************************************************
* @attention
*
* <h2><center>&copy; COPYRIGHT(c) 2014 STMicroelectronics</center></h2>
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of STMicroelectronics nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "fonts.h"
const uint8_t Font24_Table [] =
{
// @0 ' ' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @72 '!' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x01, 0x00, 0x00, // #
0x01, 0x00, 0x00, // #
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @144 '"' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x0E, 0x70, 0x00, // ### ###
0x0E, 0x70, 0x00, // ### ###
0x0E, 0x70, 0x00, // ### ###
0x04, 0x20, 0x00, // # #
0x04, 0x20, 0x00, // # #
0x04, 0x20, 0x00, // # #
0x04, 0x20, 0x00, // # #
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @216 '#' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x06, 0x60, 0x00, // ## ##
0x06, 0x60, 0x00, // ## ##
0x06, 0x60, 0x00, // ## ##
0x06, 0x60, 0x00, // ## ##
0x06, 0x60, 0x00, // ## ##
0x3F, 0xF8, 0x00, // ###########
0x3F, 0xF8, 0x00, // ###########
0x06, 0x60, 0x00, // ## ##
0x0C, 0xC0, 0x00, // ## ##
0x3F, 0xF8, 0x00, // ###########
0x3F, 0xF8, 0x00, // ###########
0x0C, 0xC0, 0x00, // ## ##
0x0C, 0xC0, 0x00, // ## ##
0x0C, 0xC0, 0x00, // ## ##
0x0C, 0xC0, 0x00, // ## ##
0x0C, 0xC0, 0x00, // ## ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @288 '$' (17 pixels wide)
0x00, 0x00, 0x00, //
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x07, 0xB0, 0x00, // #### ##
0x0F, 0xF0, 0x00, // ########
0x18, 0x70, 0x00, // ## ###
0x18, 0x70, 0x00, // ## ###
0x1C, 0x00, 0x00, // ###
0x0F, 0x80, 0x00, // #####
0x07, 0xE0, 0x00, // ######
0x00, 0xF0, 0x00, // ####
0x18, 0x30, 0x00, // ## ##
0x1C, 0x30, 0x00, // ### ##
0x1C, 0x70, 0x00, // ### ###
0x1F, 0xE0, 0x00, // ########
0x1B, 0xC0, 0x00, // ## ####
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @360 '%' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x07, 0x80, 0x00, // ####
0x0F, 0xC0, 0x00, // ######
0x1C, 0xE0, 0x00, // ### ###
0x18, 0x60, 0x00, // ## ##
0x18, 0x60, 0x00, // ## ##
0x1C, 0xE0, 0x00, // ### ###
0x0F, 0xF8, 0x00, // #########
0x07, 0xE0, 0x00, // ######
0x1F, 0xF0, 0x00, // #########
0x07, 0x38, 0x00, // ### ###
0x06, 0x18, 0x00, // ## ##
0x06, 0x18, 0x00, // ## ##
0x07, 0x38, 0x00, // ### ###
0x03, 0xF0, 0x00, // ######
0x01, 0xE0, 0x00, // ####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @432 '&' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0xF0, 0x00, // ######
0x07, 0xF0, 0x00, // #######
0x0C, 0x60, 0x00, // ## ##
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x06, 0x00, 0x00, // ##
0x07, 0x00, 0x00, // ###
0x0F, 0x9C, 0x00, // ##### ###
0x1D, 0xFC, 0x00, // ### #######
0x18, 0xF0, 0x00, // ## ####
0x18, 0x70, 0x00, // ## ###
0x0F, 0xFC, 0x00, // ##########
0x07, 0xDC, 0x00, // ##### ###
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @504 ''' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x01, 0x00, 0x00, // #
0x01, 0x00, 0x00, // #
0x01, 0x00, 0x00, // #
0x01, 0x00, 0x00, // #
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @576 '(' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x18, 0x00, // ##
0x00, 0x38, 0x00, // ###
0x00, 0x70, 0x00, // ###
0x00, 0xF0, 0x00, // ####
0x00, 0xE0, 0x00, // ###
0x00, 0xE0, 0x00, // ###
0x01, 0xC0, 0x00, // ###
0x01, 0xC0, 0x00, // ###
0x01, 0xC0, 0x00, // ###
0x01, 0xC0, 0x00, // ###
0x01, 0xC0, 0x00, // ###
0x01, 0xC0, 0x00, // ###
0x00, 0xE0, 0x00, // ###
0x00, 0xE0, 0x00, // ###
0x00, 0x70, 0x00, // ###
0x00, 0x70, 0x00, // ###
0x00, 0x38, 0x00, // ###
0x00, 0x18, 0x00, // ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @648 ')' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x18, 0x00, 0x00, // ##
0x1C, 0x00, 0x00, // ###
0x0E, 0x00, 0x00, // ###
0x0E, 0x00, 0x00, // ###
0x07, 0x00, 0x00, // ###
0x07, 0x00, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x07, 0x00, 0x00, // ###
0x07, 0x00, 0x00, // ###
0x0F, 0x00, 0x00, // ####
0x0E, 0x00, 0x00, // ###
0x1C, 0x00, 0x00, // ###
0x18, 0x00, 0x00, // ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @720 '*' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x1D, 0xB8, 0x00, // ### ## ###
0x1F, 0xF8, 0x00, // ##########
0x07, 0xE0, 0x00, // ######
0x03, 0xC0, 0x00, // ####
0x03, 0xC0, 0x00, // ####
0x06, 0x60, 0x00, // ## ##
0x06, 0x60, 0x00, // ## ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @792 '+' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x3F, 0xFC, 0x00, // ############
0x3F, 0xFC, 0x00, // ############
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @864 ',' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0xE0, 0x00, // ###
0x00, 0xC0, 0x00, // ##
0x01, 0xC0, 0x00, // ###
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x03, 0x00, 0x00, // ##
0x03, 0x00, 0x00, // ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @936 '-' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x1F, 0xF8, 0x00, // ##########
0x1F, 0xF8, 0x00, // ##########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @1008 '.' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0xC0, 0x00, // ####
0x03, 0xC0, 0x00, // ####
0x03, 0xC0, 0x00, // ####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @1080 '/' (17 pixels wide)
0x00, 0x18, 0x00, // ##
0x00, 0x18, 0x00, // ##
0x00, 0x38, 0x00, // ###
0x00, 0x30, 0x00, // ##
0x00, 0x70, 0x00, // ###
0x00, 0x60, 0x00, // ##
0x00, 0x60, 0x00, // ##
0x00, 0xC0, 0x00, // ##
0x00, 0xC0, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x03, 0x00, 0x00, // ##
0x03, 0x00, 0x00, // ##
0x06, 0x00, 0x00, // ##
0x06, 0x00, 0x00, // ##
0x0E, 0x00, 0x00, // ###
0x0C, 0x00, 0x00, // ##
0x1C, 0x00, 0x00, // ###
0x18, 0x00, 0x00, // ##
0x18, 0x00, 0x00, // ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @1152 '0' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0xC0, 0x00, // ####
0x07, 0xE0, 0x00, // ######
0x0C, 0x30, 0x00, // ## ##
0x0C, 0x30, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x0C, 0x30, 0x00, // ## ##
0x0C, 0x30, 0x00, // ## ##
0x07, 0xE0, 0x00, // ######
0x03, 0xC0, 0x00, // ####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @1224 '1' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x80, 0x00, // #
0x07, 0x80, 0x00, // ####
0x1F, 0x80, 0x00, // ######
0x1D, 0x80, 0x00, // ### ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x1F, 0xF8, 0x00, // ##########
0x1F, 0xF8, 0x00, // ##########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @1296 '2' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x07, 0xC0, 0x00, // #####
0x1F, 0xF0, 0x00, // #########
0x38, 0x30, 0x00, // ### ##
0x30, 0x18, 0x00, // ## ##
0x30, 0x18, 0x00, // ## ##
0x00, 0x18, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x60, 0x00, // ##
0x01, 0xC0, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x06, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x18, 0x00, 0x00, // ##
0x3F, 0xF8, 0x00, // ###########
0x3F, 0xF8, 0x00, // ###########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @1368 '3' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0xC0, 0x00, // ####
0x0F, 0xE0, 0x00, // #######
0x0C, 0x70, 0x00, // ## ###
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x60, 0x00, // ##
0x03, 0xC0, 0x00, // ####
0x03, 0xE0, 0x00, // #####
0x00, 0x70, 0x00, // ###
0x00, 0x18, 0x00, // ##
0x00, 0x18, 0x00, // ##
0x00, 0x18, 0x00, // ##
0x18, 0x38, 0x00, // ## ###
0x1F, 0xF0, 0x00, // #########
0x0F, 0xC0, 0x00, // ######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @1440 '4' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0xE0, 0x00, // ###
0x01, 0xE0, 0x00, // ####
0x01, 0xE0, 0x00, // ####
0x03, 0x60, 0x00, // ## ##
0x06, 0x60, 0x00, // ## ##
0x06, 0x60, 0x00, // ## ##
0x0C, 0x60, 0x00, // ## ##
0x0C, 0x60, 0x00, // ## ##
0x18, 0x60, 0x00, // ## ##
0x30, 0x60, 0x00, // ## ##
0x3F, 0xF8, 0x00, // ###########
0x3F, 0xF8, 0x00, // ###########
0x00, 0x60, 0x00, // ##
0x03, 0xF8, 0x00, // #######
0x03, 0xF8, 0x00, // #######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @1512 '5' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x1F, 0xF0, 0x00, // #########
0x1F, 0xF0, 0x00, // #########
0x18, 0x00, 0x00, // ##
0x18, 0x00, 0x00, // ##
0x18, 0x00, 0x00, // ##
0x1B, 0xC0, 0x00, // ## ####
0x1F, 0xF0, 0x00, // #########
0x1C, 0x30, 0x00, // ### ##
0x00, 0x18, 0x00, // ##
0x00, 0x18, 0x00, // ##
0x00, 0x18, 0x00, // ##
0x00, 0x18, 0x00, // ##
0x30, 0x30, 0x00, // ## ##
0x3F, 0xF0, 0x00, // ##########
0x0F, 0xC0, 0x00, // ######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @1584 '6' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0xF8, 0x00, // #####
0x03, 0xF8, 0x00, // #######
0x07, 0x00, 0x00, // ###
0x0E, 0x00, 0x00, // ###
0x0C, 0x00, 0x00, // ##
0x18, 0x00, 0x00, // ##
0x1B, 0xC0, 0x00, // ## ####
0x1F, 0xF0, 0x00, // #########
0x1C, 0x30, 0x00, // ### ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x0C, 0x38, 0x00, // ## ###
0x0F, 0xF0, 0x00, // ########
0x03, 0xE0, 0x00, // #####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @1656 '7' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x1F, 0xF8, 0x00, // ##########
0x1F, 0xF8, 0x00, // ##########
0x18, 0x18, 0x00, // ## ##
0x18, 0x38, 0x00, // ## ###
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x70, 0x00, // ###
0x00, 0x60, 0x00, // ##
0x00, 0x60, 0x00, // ##
0x00, 0xE0, 0x00, // ###
0x00, 0xC0, 0x00, // ##
0x00, 0xC0, 0x00, // ##
0x01, 0xC0, 0x00, // ###
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @1728 '8' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x07, 0xE0, 0x00, // ######
0x0F, 0xF0, 0x00, // ########
0x1C, 0x38, 0x00, // ### ###
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x0C, 0x30, 0x00, // ## ##
0x07, 0xE0, 0x00, // ######
0x07, 0xE0, 0x00, // ######
0x0C, 0x30, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x1C, 0x38, 0x00, // ### ###
0x0F, 0xF0, 0x00, // ########
0x07, 0xE0, 0x00, // ######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @1800 '9' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x07, 0xC0, 0x00, // #####
0x0F, 0xF0, 0x00, // ########
0x1C, 0x30, 0x00, // ### ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x0C, 0x38, 0x00, // ## ###
0x0F, 0xF8, 0x00, // #########
0x03, 0xD8, 0x00, // #### ##
0x00, 0x18, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x70, 0x00, // ###
0x00, 0xE0, 0x00, // ###
0x1F, 0xC0, 0x00, // #######
0x1F, 0x00, 0x00, // #####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @1872 ':' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0xC0, 0x00, // ####
0x03, 0xC0, 0x00, // ####
0x03, 0xC0, 0x00, // ####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0xC0, 0x00, // ####
0x03, 0xC0, 0x00, // ####
0x03, 0xC0, 0x00, // ####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @1944 ';' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0xF0, 0x00, // ####
0x00, 0xF0, 0x00, // ####
0x00, 0xF0, 0x00, // ####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0xE0, 0x00, // ###
0x01, 0xC0, 0x00, // ###
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x03, 0x00, 0x00, // ##
0x02, 0x00, 0x00, // #
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @2016 '<' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x1C, 0x00, // ###
0x00, 0x3C, 0x00, // ####
0x00, 0xF0, 0x00, // ####
0x03, 0xC0, 0x00, // ####
0x0F, 0x00, 0x00, // ####
0x3C, 0x00, 0x00, // ####
0xF0, 0x00, 0x00, // ####
0x3C, 0x00, 0x00, // ####
0x0F, 0x00, 0x00, // ####
0x03, 0xC0, 0x00, // ####
0x00, 0xF0, 0x00, // ####
0x00, 0x3C, 0x00, // ####
0x00, 0x1C, 0x00, // ###
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @2088 '=' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7F, 0xFC, 0x00, // #############
0x7F, 0xFC, 0x00, // #############
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7F, 0xFC, 0x00, // #############
0x7F, 0xFC, 0x00, // #############
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @2160 '>' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x70, 0x00, 0x00, // ###
0x78, 0x00, 0x00, // ####
0x1E, 0x00, 0x00, // ####
0x07, 0x80, 0x00, // ####
0x01, 0xE0, 0x00, // ####
0x00, 0x78, 0x00, // ####
0x00, 0x1E, 0x00, // ####
0x00, 0x78, 0x00, // ####
0x01, 0xE0, 0x00, // ####
0x07, 0x80, 0x00, // ####
0x1E, 0x00, 0x00, // ####
0x78, 0x00, 0x00, // ####
0x70, 0x00, 0x00, // ###
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @2232 '?' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x07, 0xC0, 0x00, // #####
0x0F, 0xE0, 0x00, // #######
0x18, 0x70, 0x00, // ## ###
0x18, 0x30, 0x00, // ## ##
0x18, 0x30, 0x00, // ## ##
0x00, 0x70, 0x00, // ###
0x00, 0xE0, 0x00, // ###
0x03, 0xC0, 0x00, // ####
0x03, 0x80, 0x00, // ###
0x03, 0x00, 0x00, // ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x07, 0x00, 0x00, // ###
0x07, 0x00, 0x00, // ###
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @2304 '@' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0xE0, 0x00, // #####
0x07, 0xF0, 0x00, // #######
0x0E, 0x38, 0x00, // ### ###
0x0C, 0x18, 0x00, // ## ##
0x18, 0x78, 0x00, // ## ####
0x18, 0xF8, 0x00, // ## #####
0x19, 0xD8, 0x00, // ## ### ##
0x19, 0x98, 0x00, // ## ## ##
0x19, 0x98, 0x00, // ## ## ##
0x19, 0x98, 0x00, // ## ## ##
0x18, 0xF8, 0x00, // ## #####
0x18, 0x78, 0x00, // ## ####
0x18, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x0E, 0x18, 0x00, // ### ##
0x07, 0xF8, 0x00, // ########
0x03, 0xE0, 0x00, // #####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @2376 'A' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x1F, 0x80, 0x00, // ######
0x1F, 0xC0, 0x00, // #######
0x01, 0xC0, 0x00, // ###
0x03, 0x60, 0x00, // ## ##
0x03, 0x60, 0x00, // ## ##
0x06, 0x30, 0x00, // ## ##
0x06, 0x30, 0x00, // ## ##
0x0C, 0x30, 0x00, // ## ##
0x0F, 0xF8, 0x00, // #########
0x1F, 0xF8, 0x00, // ##########
0x18, 0x0C, 0x00, // ## ##
0x30, 0x0C, 0x00, // ## ##
0xFC, 0x7F, 0x00, // ###### #######
0xFC, 0x7F, 0x00, // ###### #######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @2448 'B' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7F, 0xE0, 0x00, // ##########
0x7F, 0xF0, 0x00, // ###########
0x18, 0x38, 0x00, // ## ###
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x38, 0x00, // ## ###
0x1F, 0xF0, 0x00, // #########
0x1F, 0xF8, 0x00, // ##########
0x18, 0x1C, 0x00, // ## ###
0x18, 0x0C, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x7F, 0xF8, 0x00, // ############
0x7F, 0xF0, 0x00, // ###########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @2520 'C' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0xEC, 0x00, // ##### ##
0x0F, 0xFC, 0x00, // ##########
0x1C, 0x1C, 0x00, // ### ###
0x18, 0x0C, 0x00, // ## ##
0x30, 0x0C, 0x00, // ## ##
0x30, 0x00, 0x00, // ##
0x30, 0x00, 0x00, // ##
0x30, 0x00, 0x00, // ##
0x30, 0x00, 0x00, // ##
0x30, 0x00, 0x00, // ##
0x18, 0x0C, 0x00, // ## ##
0x1C, 0x1C, 0x00, // ### ###
0x0F, 0xF8, 0x00, // #########
0x03, 0xF0, 0x00, // ######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @2592 'D' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7F, 0xC0, 0x00, // #########
0x7F, 0xF0, 0x00, // ###########
0x18, 0x38, 0x00, // ## ###
0x18, 0x18, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x38, 0x00, // ## ###
0x7F, 0xF0, 0x00, // ###########
0x7F, 0xE0, 0x00, // ##########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @2664 'E' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7F, 0xF8, 0x00, // ############
0x7F, 0xF8, 0x00, // ############
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x19, 0x98, 0x00, // ## ## ##
0x19, 0x80, 0x00, // ## ##
0x1F, 0x80, 0x00, // ######
0x1F, 0x80, 0x00, // ######
0x19, 0x80, 0x00, // ## ##
0x19, 0x98, 0x00, // ## ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x7F, 0xF8, 0x00, // ############
0x7F, 0xF8, 0x00, // ############
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @2736 'F' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x3F, 0xFC, 0x00, // ############
0x3F, 0xFC, 0x00, // ############
0x0C, 0x0C, 0x00, // ## ##
0x0C, 0x0C, 0x00, // ## ##
0x0C, 0xCC, 0x00, // ## ## ##
0x0C, 0xC0, 0x00, // ## ##
0x0F, 0xC0, 0x00, // ######
0x0F, 0xC0, 0x00, // ######
0x0C, 0xC0, 0x00, // ## ##
0x0C, 0xC0, 0x00, // ## ##
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x3F, 0xC0, 0x00, // ########
0x3F, 0xC0, 0x00, // ########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @2808 'G' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0xEC, 0x00, // ##### ##
0x0F, 0xFC, 0x00, // ##########
0x1C, 0x1C, 0x00, // ### ###
0x18, 0x0C, 0x00, // ## ##
0x30, 0x0C, 0x00, // ## ##
0x30, 0x00, 0x00, // ##
0x30, 0x00, 0x00, // ##
0x30, 0xFE, 0x00, // ## #######
0x30, 0xFE, 0x00, // ## #######
0x30, 0x0C, 0x00, // ## ##
0x38, 0x0C, 0x00, // ### ##
0x1C, 0x1C, 0x00, // ### ###
0x0F, 0xFC, 0x00, // ##########
0x03, 0xF0, 0x00, // ######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @2880 'H' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7E, 0x7E, 0x00, // ###### ######
0x7E, 0x7E, 0x00, // ###### ######
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x1F, 0xF8, 0x00, // ##########
0x1F, 0xF8, 0x00, // ##########
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x7E, 0x7E, 0x00, // ###### ######
0x7E, 0x7E, 0x00, // ###### ######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @2952 'I' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x1F, 0xF8, 0x00, // ##########
0x1F, 0xF8, 0x00, // ##########
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x1F, 0xF8, 0x00, // ##########
0x1F, 0xF8, 0x00, // ##########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @3024 'J' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x07, 0xFE, 0x00, // ##########
0x07, 0xFE, 0x00, // ##########
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x30, 0x30, 0x00, // ## ##
0x30, 0x30, 0x00, // ## ##
0x30, 0x30, 0x00, // ## ##
0x30, 0x30, 0x00, // ## ##
0x30, 0x60, 0x00, // ## ##
0x3F, 0xE0, 0x00, // #########
0x0F, 0x80, 0x00, // #####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @3096 'K' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7F, 0x3E, 0x00, // ####### #####
0x7F, 0x3E, 0x00, // ####### #####
0x18, 0x30, 0x00, // ## ##
0x18, 0x60, 0x00, // ## ##
0x18, 0xC0, 0x00, // ## ##
0x19, 0x80, 0x00, // ## ##
0x1B, 0x80, 0x00, // ## ###
0x1F, 0xC0, 0x00, // #######
0x1C, 0xE0, 0x00, // ### ###
0x18, 0x70, 0x00, // ## ###
0x18, 0x30, 0x00, // ## ##
0x18, 0x38, 0x00, // ## ###
0x7F, 0x1F, 0x00, // ####### #####
0x7F, 0x1F, 0x00, // ####### #####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @3168 'L' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7F, 0x80, 0x00, // ########
0x7F, 0x80, 0x00, // ########
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x0C, 0x0C, 0x00, // ## ##
0x0C, 0x0C, 0x00, // ## ##
0x0C, 0x0C, 0x00, // ## ##
0x0C, 0x0C, 0x00, // ## ##
0x7F, 0xFC, 0x00, // #############
0x7F, 0xFC, 0x00, // #############
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @3240 'M' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0xF0, 0x0F, 0x00, // #### ####
0xF8, 0x1F, 0x00, // ##### #####
0x38, 0x1C, 0x00, // ### ###
0x3C, 0x3C, 0x00, // #### ####
0x3C, 0x3C, 0x00, // #### ####
0x36, 0x6C, 0x00, // ## ## ## ##
0x36, 0x6C, 0x00, // ## ## ## ##
0x33, 0xCC, 0x00, // ## #### ##
0x33, 0xCC, 0x00, // ## #### ##
0x31, 0x8C, 0x00, // ## ## ##
0x30, 0x0C, 0x00, // ## ##
0x30, 0x0C, 0x00, // ## ##
0xFE, 0x7F, 0x00, // ####### #######
0xFE, 0x7F, 0x00, // ####### #######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @3312 'N' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x78, 0xFE, 0x00, // #### #######
0x78, 0xFE, 0x00, // #### #######
0x1C, 0x18, 0x00, // ### ##
0x1E, 0x18, 0x00, // #### ##
0x1F, 0x18, 0x00, // ##### ##
0x1B, 0x18, 0x00, // ## ## ##
0x1B, 0x98, 0x00, // ## ### ##
0x19, 0xD8, 0x00, // ## ### ##
0x18, 0xD8, 0x00, // ## ## ##
0x18, 0xF8, 0x00, // ## #####
0x18, 0x78, 0x00, // ## ####
0x18, 0x38, 0x00, // ## ###
0x7F, 0x18, 0x00, // ####### ##
0x7F, 0x18, 0x00, // ####### ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @3384 'O' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0xC0, 0x00, // ####
0x0F, 0xF0, 0x00, // ########
0x1C, 0x38, 0x00, // ### ###
0x18, 0x18, 0x00, // ## ##
0x38, 0x1C, 0x00, // ### ###
0x30, 0x0C, 0x00, // ## ##
0x30, 0x0C, 0x00, // ## ##
0x30, 0x0C, 0x00, // ## ##
0x30, 0x0C, 0x00, // ## ##
0x38, 0x1C, 0x00, // ### ###
0x18, 0x18, 0x00, // ## ##
0x1C, 0x38, 0x00, // ### ###
0x0F, 0xF0, 0x00, // ########
0x03, 0xC0, 0x00, // ####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @3456 'P' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x3F, 0xF0, 0x00, // ##########
0x3F, 0xF8, 0x00, // ###########
0x0C, 0x1C, 0x00, // ## ###
0x0C, 0x0C, 0x00, // ## ##
0x0C, 0x0C, 0x00, // ## ##
0x0C, 0x0C, 0x00, // ## ##
0x0C, 0x18, 0x00, // ## ##
0x0F, 0xF8, 0x00, // #########
0x0F, 0xE0, 0x00, // #######
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x3F, 0xC0, 0x00, // ########
0x3F, 0xC0, 0x00, // ########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @3528 'Q' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0xC0, 0x00, // ####
0x0F, 0xF0, 0x00, // ########
0x1C, 0x38, 0x00, // ### ###
0x18, 0x18, 0x00, // ## ##
0x38, 0x1C, 0x00, // ### ###
0x30, 0x0C, 0x00, // ## ##
0x30, 0x0C, 0x00, // ## ##
0x30, 0x0C, 0x00, // ## ##
0x30, 0x0C, 0x00, // ## ##
0x38, 0x1C, 0x00, // ### ###
0x18, 0x18, 0x00, // ## ##
0x1C, 0x38, 0x00, // ### ###
0x0F, 0xF0, 0x00, // ########
0x07, 0xC0, 0x00, // #####
0x07, 0xCC, 0x00, // ##### ##
0x0F, 0xFC, 0x00, // ##########
0x0C, 0x38, 0x00, // ## ###
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @3600 'R' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7F, 0xE0, 0x00, // ##########
0x7F, 0xF0, 0x00, // ###########
0x18, 0x38, 0x00, // ## ###
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x38, 0x00, // ## ###
0x1F, 0xF0, 0x00, // #########
0x1F, 0xC0, 0x00, // #######
0x18, 0xE0, 0x00, // ## ###
0x18, 0x70, 0x00, // ## ###
0x18, 0x30, 0x00, // ## ##
0x18, 0x38, 0x00, // ## ###
0x7F, 0x1E, 0x00, // ####### ####
0x7F, 0x0E, 0x00, // ####### ###
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @3672 'S' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x07, 0xD8, 0x00, // ##### ##
0x0F, 0xF8, 0x00, // #########
0x1C, 0x38, 0x00, // ### ###
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x1E, 0x00, 0x00, // ####
0x0F, 0xC0, 0x00, // ######
0x03, 0xF0, 0x00, // ######
0x00, 0x78, 0x00, // ####
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x1C, 0x38, 0x00, // ### ###
0x1F, 0xF0, 0x00, // #########
0x1B, 0xE0, 0x00, // ## #####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @3744 'T' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x3F, 0xFC, 0x00, // ############
0x3F, 0xFC, 0x00, // ############
0x31, 0x8C, 0x00, // ## ## ##
0x31, 0x8C, 0x00, // ## ## ##
0x31, 0x8C, 0x00, // ## ## ##
0x31, 0x8C, 0x00, // ## ## ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x0F, 0xF0, 0x00, // ########
0x0F, 0xF0, 0x00, // ########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @3816 'U' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7E, 0x7E, 0x00, // ###### ######
0x7E, 0x7E, 0x00, // ###### ######
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x0C, 0x30, 0x00, // ## ##
0x0F, 0xF0, 0x00, // ########
0x03, 0xC0, 0x00, // ####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @3888 'V' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7F, 0x7F, 0x00, // ####### #######
0x7F, 0x7F, 0x00, // ####### #######
0x18, 0x0C, 0x00, // ## ##
0x0C, 0x18, 0x00, // ## ##
0x0C, 0x18, 0x00, // ## ##
0x0C, 0x18, 0x00, // ## ##
0x06, 0x30, 0x00, // ## ##
0x06, 0x30, 0x00, // ## ##
0x03, 0x60, 0x00, // ## ##
0x03, 0x60, 0x00, // ## ##
0x03, 0x60, 0x00, // ## ##
0x01, 0xC0, 0x00, // ###
0x01, 0xC0, 0x00, // ###
0x00, 0x80, 0x00, // #
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @3960 'W' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0xFE, 0x3F, 0x80, // ####### #######
0xFE, 0x3F, 0x80, // ####### #######
0x30, 0x06, 0x00, // ## ##
0x30, 0x06, 0x00, // ## ##
0x30, 0x86, 0x00, // ## # ##
0x19, 0xCC, 0x00, // ## ### ##
0x19, 0xCC, 0x00, // ## ### ##
0x1B, 0x6C, 0x00, // ## ## ## ##
0x1B, 0x6C, 0x00, // ## ## ## ##
0x1E, 0x7C, 0x00, // #### #####
0x0E, 0x38, 0x00, // ### ###
0x0E, 0x38, 0x00, // ### ###
0x0C, 0x18, 0x00, // ## ##
0x0C, 0x18, 0x00, // ## ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @4032 'X' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7E, 0x7E, 0x00, // ###### ######
0x7E, 0x7E, 0x00, // ###### ######
0x18, 0x18, 0x00, // ## ##
0x0C, 0x30, 0x00, // ## ##
0x06, 0x60, 0x00, // ## ##
0x03, 0xC0, 0x00, // ####
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x03, 0xC0, 0x00, // ####
0x06, 0x60, 0x00, // ## ##
0x0C, 0x30, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x7E, 0x7E, 0x00, // ###### ######
0x7E, 0x7E, 0x00, // ###### ######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @4104 'Y' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7C, 0x7E, 0x00, // ##### ######
0x7C, 0x7E, 0x00, // ##### ######
0x18, 0x18, 0x00, // ## ##
0x0C, 0x30, 0x00, // ## ##
0x06, 0x60, 0x00, // ## ##
0x06, 0x60, 0x00, // ## ##
0x03, 0xC0, 0x00, // ####
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x0F, 0xF0, 0x00, // ########
0x0F, 0xF0, 0x00, // ########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @4176 'Z' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x1F, 0xF8, 0x00, // ##########
0x1F, 0xF8, 0x00, // ##########
0x18, 0x18, 0x00, // ## ##
0x18, 0x30, 0x00, // ## ##
0x18, 0x60, 0x00, // ## ##
0x18, 0xC0, 0x00, // ## ##
0x01, 0x80, 0x00, // ##
0x03, 0x00, 0x00, // ##
0x06, 0x18, 0x00, // ## ##
0x0C, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x30, 0x18, 0x00, // ## ##
0x3F, 0xF8, 0x00, // ###########
0x3F, 0xF8, 0x00, // ###########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @4248 '[' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x01, 0xF0, 0x00, // #####
0x01, 0xF0, 0x00, // #####
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0xF0, 0x00, // #####
0x01, 0xF0, 0x00, // #####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @4320 '\' (17 pixels wide)
0x18, 0x00, 0x00, // ##
0x18, 0x00, 0x00, // ##
0x1C, 0x00, 0x00, // ###
0x0C, 0x00, 0x00, // ##
0x0E, 0x00, 0x00, // ###
0x06, 0x00, 0x00, // ##
0x06, 0x00, 0x00, // ##
0x03, 0x00, 0x00, // ##
0x03, 0x00, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x00, 0xC0, 0x00, // ##
0x00, 0xC0, 0x00, // ##
0x00, 0x60, 0x00, // ##
0x00, 0x60, 0x00, // ##
0x00, 0x70, 0x00, // ###
0x00, 0x30, 0x00, // ##
0x00, 0x38, 0x00, // ###
0x00, 0x18, 0x00, // ##
0x00, 0x18, 0x00, // ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @4392 ']' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x0F, 0x80, 0x00, // #####
0x0F, 0x80, 0x00, // #####
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x0F, 0x80, 0x00, // #####
0x0F, 0x80, 0x00, // #####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @4464 '^' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x80, 0x00, // #
0x01, 0xC0, 0x00, // ###
0x03, 0xE0, 0x00, // #####
0x07, 0x70, 0x00, // ### ###
0x06, 0x30, 0x00, // ## ##
0x0C, 0x18, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x10, 0x04, 0x00, // # #
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @4536 '_' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0xFF, 0xFF, 0x00, // ################
0xFF, 0xFF, 0x00, // ################
// @4608 '`' (17 pixels wide)
0x00, 0x00, 0x00, //
0x03, 0x00, 0x00, // ##
0x03, 0x80, 0x00, // ###
0x00, 0xE0, 0x00, // ###
0x00, 0x60, 0x00, // ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @4680 'a' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x0F, 0xC0, 0x00, // ######
0x1F, 0xE0, 0x00, // ########
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x07, 0xF0, 0x00, // #######
0x1F, 0xF0, 0x00, // #########
0x38, 0x30, 0x00, // ### ##
0x30, 0x30, 0x00, // ## ##
0x30, 0x70, 0x00, // ## ###
0x1F, 0xFC, 0x00, // ###########
0x0F, 0xBC, 0x00, // ##### ####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @4752 'b' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x78, 0x00, 0x00, // ####
0x78, 0x00, 0x00, // ####
0x18, 0x00, 0x00, // ##
0x18, 0x00, 0x00, // ##
0x1B, 0xE0, 0x00, // ## #####
0x1F, 0xF8, 0x00, // ##########
0x1C, 0x18, 0x00, // ### ##
0x18, 0x0C, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x1C, 0x18, 0x00, // ### ##
0x7F, 0xF8, 0x00, // ############
0x7B, 0xE0, 0x00, // #### #####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @4824 'c' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0xEC, 0x00, // ##### ##
0x0F, 0xFC, 0x00, // ##########
0x1C, 0x1C, 0x00, // ### ###
0x38, 0x0C, 0x00, // ### ##
0x30, 0x0C, 0x00, // ## ##
0x30, 0x00, 0x00, // ##
0x30, 0x00, 0x00, // ##
0x38, 0x0C, 0x00, // ### ##
0x1C, 0x1C, 0x00, // ### ###
0x0F, 0xF8, 0x00, // #########
0x03, 0xF0, 0x00, // ######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @4896 'd' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x78, 0x00, // ####
0x00, 0x78, 0x00, // ####
0x00, 0x18, 0x00, // ##
0x00, 0x18, 0x00, // ##
0x07, 0xD8, 0x00, // ##### ##
0x1F, 0xF8, 0x00, // ##########
0x18, 0x38, 0x00, // ## ###
0x30, 0x18, 0x00, // ## ##
0x30, 0x18, 0x00, // ## ##
0x30, 0x18, 0x00, // ## ##
0x30, 0x18, 0x00, // ## ##
0x30, 0x18, 0x00, // ## ##
0x18, 0x38, 0x00, // ## ###
0x1F, 0xFE, 0x00, // ############
0x07, 0xDE, 0x00, // ##### ####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @4968 'e' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x07, 0xE0, 0x00, // ######
0x1F, 0xF8, 0x00, // ##########
0x18, 0x18, 0x00, // ## ##
0x30, 0x0C, 0x00, // ## ##
0x3F, 0xFC, 0x00, // ############
0x3F, 0xFC, 0x00, // ############
0x30, 0x00, 0x00, // ##
0x30, 0x00, 0x00, // ##
0x18, 0x0C, 0x00, // ## ##
0x1F, 0xFC, 0x00, // ###########
0x07, 0xF0, 0x00, // #######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @5040 'f' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x01, 0xFC, 0x00, // #######
0x03, 0xFC, 0x00, // ########
0x06, 0x00, 0x00, // ##
0x06, 0x00, 0x00, // ##
0x3F, 0xF8, 0x00, // ###########
0x3F, 0xF8, 0x00, // ###########
0x06, 0x00, 0x00, // ##
0x06, 0x00, 0x00, // ##
0x06, 0x00, 0x00, // ##
0x06, 0x00, 0x00, // ##
0x06, 0x00, 0x00, // ##
0x06, 0x00, 0x00, // ##
0x06, 0x00, 0x00, // ##
0x3F, 0xF0, 0x00, // ##########
0x3F, 0xF0, 0x00, // ##########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @5112 'g' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x07, 0xDE, 0x00, // ##### ####
0x1F, 0xFE, 0x00, // ############
0x18, 0x38, 0x00, // ## ###
0x30, 0x18, 0x00, // ## ##
0x30, 0x18, 0x00, // ## ##
0x30, 0x18, 0x00, // ## ##
0x30, 0x18, 0x00, // ## ##
0x30, 0x18, 0x00, // ## ##
0x18, 0x38, 0x00, // ## ###
0x1F, 0xF8, 0x00, // ##########
0x07, 0xD8, 0x00, // ##### ##
0x00, 0x18, 0x00, // ##
0x00, 0x18, 0x00, // ##
0x00, 0x38, 0x00, // ###
0x0F, 0xF0, 0x00, // ########
0x0F, 0xC0, 0x00, // ######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @5184 'h' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x78, 0x00, 0x00, // ####
0x78, 0x00, 0x00, // ####
0x18, 0x00, 0x00, // ##
0x18, 0x00, 0x00, // ##
0x1B, 0xE0, 0x00, // ## #####
0x1F, 0xF0, 0x00, // #########
0x1C, 0x38, 0x00, // ### ###
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x7E, 0x7E, 0x00, // ###### ######
0x7E, 0x7E, 0x00, // ###### ######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @5256 'i' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x1F, 0x80, 0x00, // ######
0x1F, 0x80, 0x00, // ######
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x3F, 0xFC, 0x00, // ############
0x3F, 0xFC, 0x00, // ############
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @5328 'j' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0xC0, 0x00, // ##
0x00, 0xC0, 0x00, // ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x1F, 0xF0, 0x00, // #########
0x1F, 0xF0, 0x00, // #########
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x30, 0x00, // ##
0x00, 0x70, 0x00, // ###
0x1F, 0xE0, 0x00, // ########
0x1F, 0x80, 0x00, // ######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @5400 'k' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x3C, 0x00, 0x00, // ####
0x3C, 0x00, 0x00, // ####
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x0C, 0xF8, 0x00, // ## #####
0x0C, 0xF8, 0x00, // ## #####
0x0C, 0xC0, 0x00, // ## ##
0x0D, 0x80, 0x00, // ## ##
0x0F, 0x80, 0x00, // #####
0x0F, 0x00, 0x00, // ####
0x0F, 0x80, 0x00, // #####
0x0D, 0xC0, 0x00, // ## ###
0x0C, 0xE0, 0x00, // ## ###
0x3C, 0x7C, 0x00, // #### #####
0x3C, 0x7C, 0x00, // #### #####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @5472 'l' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x1F, 0x80, 0x00, // ######
0x1F, 0x80, 0x00, // ######
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x3F, 0xFC, 0x00, // ############
0x3F, 0xFC, 0x00, // ############
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @5544 'm' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0xF7, 0x78, 0x00, // #### ### ####
0xFF, 0xFC, 0x00, // ##############
0x39, 0xCC, 0x00, // ### ### ##
0x31, 0x8C, 0x00, // ## ## ##
0x31, 0x8C, 0x00, // ## ## ##
0x31, 0x8C, 0x00, // ## ## ##
0x31, 0x8C, 0x00, // ## ## ##
0x31, 0x8C, 0x00, // ## ## ##
0x31, 0x8C, 0x00, // ## ## ##
0xFD, 0xEF, 0x00, // ###### #### ####
0xFD, 0xEF, 0x00, // ###### #### ####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @5616 'n' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7B, 0xE0, 0x00, // #### #####
0x7F, 0xF0, 0x00, // ###########
0x1C, 0x38, 0x00, // ### ###
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x7E, 0x7E, 0x00, // ###### ######
0x7E, 0x7E, 0x00, // ###### ######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @5688 'o' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x03, 0xC0, 0x00, // ####
0x0F, 0xF0, 0x00, // ########
0x1C, 0x38, 0x00, // ### ###
0x38, 0x1C, 0x00, // ### ###
0x30, 0x0C, 0x00, // ## ##
0x30, 0x0C, 0x00, // ## ##
0x30, 0x0C, 0x00, // ## ##
0x38, 0x1C, 0x00, // ### ###
0x1C, 0x38, 0x00, // ### ###
0x0F, 0xF0, 0x00, // ########
0x03, 0xC0, 0x00, // ####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @5760 'p' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7B, 0xE0, 0x00, // #### #####
0x7F, 0xF8, 0x00, // ############
0x1C, 0x18, 0x00, // ### ##
0x18, 0x0C, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x18, 0x0C, 0x00, // ## ##
0x1C, 0x18, 0x00, // ### ##
0x1F, 0xF8, 0x00, // ##########
0x1B, 0xE0, 0x00, // ## #####
0x18, 0x00, 0x00, // ##
0x18, 0x00, 0x00, // ##
0x18, 0x00, 0x00, // ##
0x7F, 0x00, 0x00, // #######
0x7F, 0x00, 0x00, // #######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @5832 'q' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x07, 0xDE, 0x00, // ##### ####
0x1F, 0xFE, 0x00, // ############
0x18, 0x38, 0x00, // ## ###
0x30, 0x18, 0x00, // ## ##
0x30, 0x18, 0x00, // ## ##
0x30, 0x18, 0x00, // ## ##
0x30, 0x18, 0x00, // ## ##
0x30, 0x18, 0x00, // ## ##
0x18, 0x38, 0x00, // ## ###
0x1F, 0xF8, 0x00, // ##########
0x07, 0xD8, 0x00, // ##### ##
0x00, 0x18, 0x00, // ##
0x00, 0x18, 0x00, // ##
0x00, 0x18, 0x00, // ##
0x00, 0xFE, 0x00, // #######
0x00, 0xFE, 0x00, // #######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @5904 'r' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x3E, 0x78, 0x00, // ##### ####
0x3E, 0xFC, 0x00, // ##### ######
0x07, 0xCC, 0x00, // ##### ##
0x07, 0x00, 0x00, // ###
0x06, 0x00, 0x00, // ##
0x06, 0x00, 0x00, // ##
0x06, 0x00, 0x00, // ##
0x06, 0x00, 0x00, // ##
0x06, 0x00, 0x00, // ##
0x3F, 0xF0, 0x00, // ##########
0x3F, 0xF0, 0x00, // ##########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @5976 's' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x07, 0xF8, 0x00, // ########
0x0F, 0xF8, 0x00, // #########
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x1F, 0x80, 0x00, // ######
0x0F, 0xF0, 0x00, // ########
0x00, 0xF8, 0x00, // #####
0x18, 0x18, 0x00, // ## ##
0x18, 0x38, 0x00, // ## ###
0x1F, 0xF0, 0x00, // #########
0x1F, 0xE0, 0x00, // ########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @6048 't' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x3F, 0xF0, 0x00, // ##########
0x3F, 0xF0, 0x00, // ##########
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x0C, 0x00, 0x00, // ##
0x0C, 0x1C, 0x00, // ## ###
0x07, 0xFC, 0x00, // #########
0x03, 0xF0, 0x00, // ######
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @6120 'u' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x78, 0x78, 0x00, // #### ####
0x78, 0x78, 0x00, // #### ####
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x18, 0x38, 0x00, // ## ###
0x0F, 0xFE, 0x00, // ###########
0x07, 0xDE, 0x00, // ##### ####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @6192 'v' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7C, 0x3E, 0x00, // ##### #####
0x7C, 0x3E, 0x00, // ##### #####
0x18, 0x18, 0x00, // ## ##
0x18, 0x18, 0x00, // ## ##
0x0C, 0x30, 0x00, // ## ##
0x0C, 0x30, 0x00, // ## ##
0x06, 0x60, 0x00, // ## ##
0x06, 0x60, 0x00, // ## ##
0x07, 0xE0, 0x00, // ######
0x03, 0xC0, 0x00, // ####
0x03, 0xC0, 0x00, // ####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @6264 'w' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x78, 0x3C, 0x00, // #### ####
0x78, 0x3C, 0x00, // #### ####
0x31, 0x18, 0x00, // ## # ##
0x33, 0x98, 0x00, // ## ### ##
0x33, 0x98, 0x00, // ## ### ##
0x1A, 0xB0, 0x00, // ## # # ##
0x1E, 0xF0, 0x00, // #### ####
0x1E, 0xF0, 0x00, // #### ####
0x1C, 0x60, 0x00, // ### ##
0x0C, 0x60, 0x00, // ## ##
0x0C, 0x60, 0x00, // ## ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @6336 'x' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x3E, 0x7C, 0x00, // ##### #####
0x3E, 0x7C, 0x00, // ##### #####
0x0C, 0x30, 0x00, // ## ##
0x06, 0x60, 0x00, // ## ##
0x03, 0xC0, 0x00, // ####
0x01, 0x80, 0x00, // ##
0x03, 0xC0, 0x00, // ####
0x06, 0x60, 0x00, // ## ##
0x0C, 0x30, 0x00, // ## ##
0x3E, 0x7C, 0x00, // ##### #####
0x3E, 0x7C, 0x00, // ##### #####
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @6408 'y' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x7E, 0x1F, 0x00, // ###### #####
0x7E, 0x1F, 0x00, // ###### #####
0x18, 0x0C, 0x00, // ## ##
0x0C, 0x18, 0x00, // ## ##
0x0C, 0x18, 0x00, // ## ##
0x06, 0x30, 0x00, // ## ##
0x06, 0x30, 0x00, // ## ##
0x03, 0x60, 0x00, // ## ##
0x03, 0xE0, 0x00, // #####
0x01, 0xC0, 0x00, // ###
0x00, 0xC0, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x03, 0x00, 0x00, // ##
0x3F, 0xC0, 0x00, // ########
0x3F, 0xC0, 0x00, // ########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @6480 'z' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x1F, 0xF8, 0x00, // ##########
0x1F, 0xF8, 0x00, // ##########
0x18, 0x30, 0x00, // ## ##
0x18, 0x60, 0x00, // ## ##
0x00, 0xC0, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x03, 0x00, 0x00, // ##
0x06, 0x18, 0x00, // ## ##
0x0C, 0x18, 0x00, // ## ##
0x1F, 0xF8, 0x00, // ##########
0x1F, 0xF8, 0x00, // ##########
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @6552 '{' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0xE0, 0x00, // ###
0x01, 0xE0, 0x00, // ####
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x03, 0x80, 0x00, // ###
0x07, 0x00, 0x00, // ###
0x03, 0x80, 0x00, // ###
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0xE0, 0x00, // ####
0x00, 0xE0, 0x00, // ###
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @6624 '|' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @6696 '}' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x07, 0x00, 0x00, // ###
0x07, 0x80, 0x00, // ####
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0xC0, 0x00, // ###
0x00, 0xE0, 0x00, // ###
0x01, 0xC0, 0x00, // ###
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x01, 0x80, 0x00, // ##
0x07, 0x80, 0x00, // ####
0x07, 0x00, 0x00, // ###
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
// @6768 '~' (17 pixels wide)
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x0E, 0x00, 0x00, // ###
0x1F, 0x18, 0x00, // ##### ##
0x3B, 0xB8, 0x00, // ### ### ###
0x31, 0xF0, 0x00, // ## #####
0x00, 0xE0, 0x00, // ###
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
0x00, 0x00, 0x00, //
};
sFONT Font24 = {
Font24_Table,
17, /* Width */
24, /* Height */
};
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
/**
******************************************************************************
* @file Font8.c
* @author MCD Application Team
* @version V1.0.0
* @date 18-February-2014
* @brief This file provides text Font8 for STM32xx-EVAL's LCD driver.
******************************************************************************
* @attention
*
* <h2><center>&copy; COPYRIGHT(c) 2014 STMicroelectronics</center></h2>
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of STMicroelectronics nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "fonts.h"
//
// Font data for Courier New 12pt
//
const uint8_t Font8_Table[] =
{
// @0 ' ' (5 pixels wide)
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
// @8 '!' (5 pixels wide)
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x00, //
0x20, // #
0x00, //
0x00, //
// @16 '"' (5 pixels wide)
0x50, // # #
0x50, // # #
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
// @24 '#' (5 pixels wide)
0x28, // # #
0x50, // # #
0xF8, // #####
0x50, // # #
0xF8, // #####
0x50, // # #
0xA0, // # #
0x00, //
// @32 '$' (5 pixels wide)
0x20, // #
0x30, // ##
0x60, // ##
0x30, // ##
0x10, // #
0x60, // ##
0x20, // #
0x00, //
// @40 '%' (5 pixels wide)
0x20, // #
0x20, // #
0x18, // ##
0x60, // ##
0x10, // #
0x10, // #
0x00, //
0x00, //
// @48 '&' (5 pixels wide)
0x00, //
0x38, // ###
0x20, // #
0x60, // ##
0x50, // # #
0x78, // ####
0x00, //
0x00, //
// @56 ''' (5 pixels wide)
0x20, // #
0x20, // #
0x20, // #
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
// @64 '(' (5 pixels wide)
0x10, // #
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x10, // #
0x00, //
// @72 ')' (5 pixels wide)
0x40, // #
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x40, // #
0x00, //
// @80 '*' (5 pixels wide)
0x20, // #
0x70, // ###
0x20, // #
0x50, // # #
0x00, //
0x00, //
0x00, //
0x00, //
// @88 '+' (5 pixels wide)
0x00, //
0x20, // #
0x20, // #
0xF8, // #####
0x20, // #
0x20, // #
0x00, //
0x00, //
// @96 ',' (5 pixels wide)
0x00, //
0x00, //
0x00, //
0x00, //
0x10, // #
0x20, // #
0x20, // #
0x00, //
// @104 '-' (5 pixels wide)
0x00, //
0x00, //
0x00, //
0x70, // ###
0x00, //
0x00, //
0x00, //
0x00, //
// @112 '.' (5 pixels wide)
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x20, // #
0x00, //
0x00, //
// @120 '/' (5 pixels wide)
0x10, // #
0x20, // #
0x20, // #
0x20, // #
0x40, // #
0x40, // #
0x80, // #
0x00, //
// @128 '0' (5 pixels wide)
0x20, // #
0x50, // # #
0x50, // # #
0x50, // # #
0x50, // # #
0x20, // #
0x00, //
0x00, //
// @136 '1' (5 pixels wide)
0x60, // ##
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0xF8, // #####
0x00, //
0x00, //
// @144 '2' (5 pixels wide)
0x20, // #
0x50, // # #
0x20, // #
0x20, // #
0x40, // #
0x70, // ###
0x00, //
0x00, //
// @152 '3' (5 pixels wide)
0x20, // #
0x50, // # #
0x10, // #
0x20, // #
0x10, // #
0x60, // ##
0x00, //
0x00, //
// @160 '4' (5 pixels wide)
0x10, // #
0x30, // ##
0x50, // # #
0x78, // ####
0x10, // #
0x38, // ###
0x00, //
0x00, //
// @168 '5' (5 pixels wide)
0x70, // ###
0x40, // #
0x60, // ##
0x10, // #
0x50, // # #
0x20, // #
0x00, //
0x00, //
// @176 '6' (5 pixels wide)
0x30, // ##
0x40, // #
0x60, // ##
0x50, // # #
0x50, // # #
0x60, // ##
0x00, //
0x00, //
// @184 '7' (5 pixels wide)
0x70, // ###
0x50, // # #
0x10, // #
0x20, // #
0x20, // #
0x20, // #
0x00, //
0x00, //
// @192 '8' (5 pixels wide)
0x20, // #
0x50, // # #
0x20, // #
0x50, // # #
0x50, // # #
0x20, // #
0x00, //
0x00, //
// @200 '9' (5 pixels wide)
0x30, // ##
0x50, // # #
0x50, // # #
0x30, // ##
0x10, // #
0x60, // ##
0x00, //
0x00, //
// @208 ':' (5 pixels wide)
0x00, //
0x00, //
0x20, // #
0x00, //
0x00, //
0x20, // #
0x00, //
0x00, //
// @216 ';' (5 pixels wide)
0x00, //
0x00, //
0x10, // #
0x00, //
0x10, // #
0x20, // #
0x00, //
0x00, //
// @224 '<' (5 pixels wide)
0x00, //
0x10, // #
0x20, // #
0xC0, // ##
0x20, // #
0x10, // #
0x00, //
0x00, //
// @232 '=' (5 pixels wide)
0x00, //
0x70, // ###
0x00, //
0x70, // ###
0x00, //
0x00, //
0x00, //
0x00, //
// @240 '>' (5 pixels wide)
0x00, //
0x40, // #
0x20, // #
0x18, // ##
0x20, // #
0x40, // #
0x00, //
0x00, //
// @248 '?' (5 pixels wide)
0x20, // #
0x50, // # #
0x10, // #
0x20, // #
0x00, //
0x20, // #
0x00, //
0x00, //
// @256 '@' (5 pixels wide)
0x30, // ##
0x48, // # #
0x48, // # #
0x58, // # ##
0x48, // # #
0x40, // #
0x38, // ###
0x00, //
// @264 'A' (5 pixels wide)
0x60, // ##
0x20, // #
0x50, // # #
0x70, // ###
0x88, // # #
0xD8, // ## ##
0x00, //
0x00, //
// @272 'B' (5 pixels wide)
0xF0, // ####
0x48, // # #
0x70, // ###
0x48, // # #
0x48, // # #
0xF0, // ####
0x00, //
0x00, //
// @280 'C' (5 pixels wide)
0x70, // ###
0x50, // # #
0x40, // #
0x40, // #
0x40, // #
0x30, // ##
0x00, //
0x00, //
// @288 'D' (5 pixels wide)
0xF0, // ####
0x48, // # #
0x48, // # #
0x48, // # #
0x48, // # #
0xF0, // ####
0x00, //
0x00, //
// @296 'E' (5 pixels wide)
0xF8, // #####
0x48, // # #
0x60, // ##
0x40, // #
0x48, // # #
0xF8, // #####
0x00, //
0x00, //
// @304 'F' (5 pixels wide)
0xF8, // #####
0x48, // # #
0x60, // ##
0x40, // #
0x40, // #
0xE0, // ###
0x00, //
0x00, //
// @312 'G' (5 pixels wide)
0x70, // ###
0x40, // #
0x40, // #
0x58, // # ##
0x50, // # #
0x30, // ##
0x00, //
0x00, //
// @320 'H' (5 pixels wide)
0xE8, // ### #
0x48, // # #
0x78, // ####
0x48, // # #
0x48, // # #
0xE8, // ### #
0x00, //
0x00, //
// @328 'I' (5 pixels wide)
0x70, // ###
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x70, // ###
0x00, //
0x00, //
// @336 'J' (5 pixels wide)
0x38, // ###
0x10, // #
0x10, // #
0x50, // # #
0x50, // # #
0x20, // #
0x00, //
0x00, //
// @344 'K' (5 pixels wide)
0xD8, // ## ##
0x50, // # #
0x60, // ##
0x70, // ###
0x50, // # #
0xD8, // ## ##
0x00, //
0x00, //
// @352 'L' (5 pixels wide)
0xE0, // ###
0x40, // #
0x40, // #
0x40, // #
0x48, // # #
0xF8, // #####
0x00, //
0x00, //
// @360 'M' (5 pixels wide)
0xD8, // ## ##
0xD8, // ## ##
0xD8, // ## ##
0xA8, // # # #
0x88, // # #
0xD8, // ## ##
0x00, //
0x00, //
// @368 'N' (5 pixels wide)
0xD8, // ## ##
0x68, // ## #
0x68, // ## #
0x58, // # ##
0x58, // # ##
0xE8, // ### #
0x00, //
0x00, //
// @376 'O' (5 pixels wide)
0x30, // ##
0x48, // # #
0x48, // # #
0x48, // # #
0x48, // # #
0x30, // ##
0x00, //
0x00, //
// @384 'P' (5 pixels wide)
0xF0, // ####
0x48, // # #
0x48, // # #
0x70, // ###
0x40, // #
0xE0, // ###
0x00, //
0x00, //
// @392 'Q' (5 pixels wide)
0x30, // ##
0x48, // # #
0x48, // # #
0x48, // # #
0x48, // # #
0x30, // ##
0x18, // ##
0x00, //
// @400 'R' (5 pixels wide)
0xF0, // ####
0x48, // # #
0x48, // # #
0x70, // ###
0x48, // # #
0xE8, // ### #
0x00, //
0x00, //
// @408 'S' (5 pixels wide)
0x70, // ###
0x50, // # #
0x20, // #
0x10, // #
0x50, // # #
0x70, // ###
0x00, //
0x00, //
// @416 'T' (5 pixels wide)
0xF8, // #####
0xA8, // # # #
0x20, // #
0x20, // #
0x20, // #
0x70, // ###
0x00, //
0x00, //
// @424 'U' (5 pixels wide)
0xD8, // ## ##
0x48, // # #
0x48, // # #
0x48, // # #
0x48, // # #
0x30, // ##
0x00, //
0x00, //
// @432 'V' (5 pixels wide)
0xD8, // ## ##
0x88, // # #
0x48, // # #
0x50, // # #
0x50, // # #
0x30, // ##
0x00, //
0x00, //
// @440 'W' (5 pixels wide)
0xD8, // ## ##
0x88, // # #
0xA8, // # # #
0xA8, // # # #
0xA8, // # # #
0x50, // # #
0x00, //
0x00, //
// @448 'X' (5 pixels wide)
0xD8, // ## ##
0x50, // # #
0x20, // #
0x20, // #
0x50, // # #
0xD8, // ## ##
0x00, //
0x00, //
// @456 'Y' (5 pixels wide)
0xD8, // ## ##
0x88, // # #
0x50, // # #
0x20, // #
0x20, // #
0x70, // ###
0x00, //
0x00, //
// @464 'Z' (5 pixels wide)
0x78, // ####
0x48, // # #
0x10, // #
0x20, // #
0x48, // # #
0x78, // ####
0x00, //
0x00, //
// @472 '[' (5 pixels wide)
0x30, // ##
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x30, // ##
0x00, //
// @480 '\' (5 pixels wide)
0x80, // #
0x40, // #
0x40, // #
0x20, // #
0x20, // #
0x20, // #
0x10, // #
0x00, //
// @488 ']' (5 pixels wide)
0x60, // ##
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x60, // ##
0x00, //
// @496 '^' (5 pixels wide)
0x20, // #
0x20, // #
0x50, // # #
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
// @504 '_' (5 pixels wide)
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0xF8, // #####
// @512 '`' (5 pixels wide)
0x20, // #
0x10, // #
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
0x00, //
// @520 'a' (5 pixels wide)
0x00, //
0x00, //
0x30, // ##
0x10, // #
0x70, // ###
0x78, // ####
0x00, //
0x00, //
// @528 'b' (5 pixels wide)
0xC0, // ##
0x40, // #
0x70, // ###
0x48, // # #
0x48, // # #
0xF0, // ####
0x00, //
0x00, //
// @536 'c' (5 pixels wide)
0x00, //
0x00, //
0x70, // ###
0x40, // #
0x40, // #
0x70, // ###
0x00, //
0x00, //
// @544 'd' (5 pixels wide)
0x18, // ##
0x08, // #
0x38, // ###
0x48, // # #
0x48, // # #
0x38, // ###
0x00, //
0x00, //
// @552 'e' (5 pixels wide)
0x00, //
0x00, //
0x70, // ###
0x70, // ###
0x40, // #
0x30, // ##
0x00, //
0x00, //
// @560 'f' (5 pixels wide)
0x10, // #
0x20, // #
0x70, // ###
0x20, // #
0x20, // #
0x70, // ###
0x00, //
0x00, //
// @568 'g' (5 pixels wide)
0x00, //
0x00, //
0x38, // ###
0x48, // # #
0x48, // # #
0x38, // ###
0x08, // #
0x30, // ##
// @576 'h' (5 pixels wide)
0xC0, // ##
0x40, // #
0x70, // ###
0x48, // # #
0x48, // # #
0xE8, // ### #
0x00, //
0x00, //
// @584 'i' (5 pixels wide)
0x20, // #
0x00, //
0x60, // ##
0x20, // #
0x20, // #
0x70, // ###
0x00, //
0x00, //
// @592 'j' (5 pixels wide)
0x20, // #
0x00, //
0x70, // ###
0x10, // #
0x10, // #
0x10, // #
0x10, // #
0x70, // ###
// @600 'k' (5 pixels wide)
0xC0, // ##
0x40, // #
0x58, // # ##
0x70, // ###
0x50, // # #
0xD8, // ## ##
0x00, //
0x00, //
// @608 'l' (5 pixels wide)
0x60, // ##
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x70, // ###
0x00, //
0x00, //
// @616 'm' (5 pixels wide)
0x00, //
0x00, //
0xD0, // ## #
0xA8, // # # #
0xA8, // # # #
0xA8, // # # #
0x00, //
0x00, //
// @624 'n' (5 pixels wide)
0x00, //
0x00, //
0xF0, // ####
0x48, // # #
0x48, // # #
0xC8, // ## #
0x00, //
0x00, //
// @632 'o' (5 pixels wide)
0x00, //
0x00, //
0x30, // ##
0x48, // # #
0x48, // # #
0x30, // ##
0x00, //
0x00, //
// @640 'p' (5 pixels wide)
0x00, //
0x00, //
0xF0, // ####
0x48, // # #
0x48, // # #
0x70, // ###
0x40, // #
0xE0, // ###
// @648 'q' (5 pixels wide)
0x00, //
0x00, //
0x38, // ###
0x48, // # #
0x48, // # #
0x38, // ###
0x08, // #
0x18, // ##
// @656 'r' (5 pixels wide)
0x00, //
0x00, //
0x78, // ####
0x20, // #
0x20, // #
0x70, // ###
0x00, //
0x00, //
// @664 's' (5 pixels wide)
0x00, //
0x00, //
0x30, // ##
0x20, // #
0x10, // #
0x60, // ##
0x00, //
0x00, //
// @672 't' (5 pixels wide)
0x00, //
0x40, // #
0xF0, // ####
0x40, // #
0x48, // # #
0x30, // ##
0x00, //
0x00, //
// @680 'u' (5 pixels wide)
0x00, //
0x00, //
0xD8, // ## ##
0x48, // # #
0x48, // # #
0x38, // ###
0x00, //
0x00, //
// @688 'v' (5 pixels wide)
0x00, //
0x00, //
0xC8, // ## #
0x48, // # #
0x30, // ##
0x30, // ##
0x00, //
0x00, //
// @696 'w' (5 pixels wide)
0x00, //
0x00, //
0xD8, // ## ##
0xA8, // # # #
0xA8, // # # #
0x50, // # #
0x00, //
0x00, //
// @704 'x' (5 pixels wide)
0x00, //
0x00, //
0x48, // # #
0x30, // ##
0x30, // ##
0x48, // # #
0x00, //
0x00, //
// @712 'y' (5 pixels wide)
0x00, //
0x00, //
0xD8, // ## ##
0x50, // # #
0x50, // # #
0x20, // #
0x20, // #
0x60, // ##
// @720 'z' (5 pixels wide)
0x00, //
0x00, //
0x78, // ####
0x50, // # #
0x28, // # #
0x78, // ####
0x00, //
0x00, //
// @728 '{' (5 pixels wide)
0x10, // #
0x20, // #
0x20, // #
0x60, // ##
0x20, // #
0x20, // #
0x10, // #
0x00, //
// @736 '|' (5 pixels wide)
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x20, // #
0x00, //
// @744 '}' (5 pixels wide)
0x40, // #
0x20, // #
0x20, // #
0x30, // ##
0x20, // #
0x20, // #
0x40, // #
0x00, //
// @752 '~' (5 pixels wide)
0x00, //
0x00, //
0x00, //
0x28, // # #
0x50, // # #
0x00, //
0x00, //
0x00, //
};
sFONT Font8 = {
Font8_Table,
5, /* Width */
8, /* Height */
};
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
/**
******************************************************************************
* @file fonts.h
* @author MCD Application Team
* @version V1.0.0
* @date 18-February-2014
* @brief Header for fonts.c file
******************************************************************************
* @attention
*
* <h2><center>&copy; COPYRIGHT(c) 2014 STMicroelectronics</center></h2>
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of STMicroelectronics nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************
*/
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __FONTS_H
#define __FONTS_H
/* Max size of bitmap will based on a font24 (17x24) */
#define MAX_HEIGHT_FONT 24
#define MAX_WIDTH_FONT 17
#define OFFSET_BITMAP 54
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
typedef struct _tFont
{
const uint8_t *table;
uint16_t Width;
uint16_t Height;
} sFONT;
extern sFONT Font24;
extern sFONT Font20;
extern sFONT Font16;
extern sFONT Font12;
extern sFONT Font8;
#ifdef __cplusplus
}
#endif
#endif /* __FONTS_H */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
aux_source_directory(. DIR_LCD_SRCS)
include_directories(../config)
include_directories(../font)
add_library(lcd ${DIR_LCD_SRCS})
target_link_libraries(lcd PUBLIC config font pico_stdlib)
/*****************************************************************************
* | File : LCD_Driver.c
* | Author : Waveshare team
* | Function : LCD Drive function
* | Info :
* Image scanning
* Please use progressive scanning to generate images or fonts
*----------------
* | This version: V1.0
* | Date : 2018-01-11
* | Info : Basic version
*
******************************************************************************/
/**************************Intermediate driver layer**************************/
#include "LCD_Driver.h"
LCD_DIS sLCD_DIS;
uint8_t id;
/*******************************************************************************
function:
Hardware reset
*******************************************************************************/
static void LCD_Reset(void)
{
DEV_Digital_Write(LCD_RST_PIN,1);
Driver_Delay_ms(500);
DEV_Digital_Write(LCD_RST_PIN,0);
Driver_Delay_ms(500);
DEV_Digital_Write(LCD_RST_PIN,1);
Driver_Delay_ms(500);
}
void PWM_SetValue(uint16_t duty)
{
uint slice_num = pwm_gpio_to_slice_num(LCD_BKL_PIN);
pwm_set_wrap(slice_num, 10000);
pwm_set_chan_level(slice_num, PWM_CHAN_B, duty*10);
pwm_set_enabled(slice_num, true);
}
void LCD_SetBackLight(uint16_t value)
{
PWM_SetValue(value);
}
/*******************************************************************************
function:
Write register address and data
*******************************************************************************/
void LCD_WriteReg(uint8_t Reg)
{
DEV_Digital_Write(LCD_DC_PIN,0);
DEV_Digital_Write(LCD_CS_PIN,0);
SPI4W_Write_Byte(Reg);
DEV_Digital_Write(LCD_CS_PIN,1);
}
void LCD_WriteData(uint16_t Data)
{
if(LCD_2_8 == id){
DEV_Digital_Write(LCD_DC_PIN,1);
DEV_Digital_Write(LCD_CS_PIN,0);
SPI4W_Write_Byte((uint8_t)Data);
DEV_Digital_Write(LCD_CS_PIN,1);
}else{
DEV_Digital_Write(LCD_DC_PIN,1);
DEV_Digital_Write(LCD_CS_PIN,0);
SPI4W_Write_Byte(Data >> 8);
SPI4W_Write_Byte(Data & 0XFF);
DEV_Digital_Write(LCD_CS_PIN,1);
}
}
/*******************************************************************************
function:
Write register data
*******************************************************************************/
static void LCD_Write_AllData(uint16_t Data, uint32_t DataLen)
{
uint32_t i;
DEV_Digital_Write(LCD_DC_PIN,1);
DEV_Digital_Write(LCD_CS_PIN,0);
for(i = 0; i < DataLen; i++) {
SPI4W_Write_Byte(Data >> 8);
SPI4W_Write_Byte(Data & 0XFF);
}
DEV_Digital_Write(LCD_CS_PIN,1);
}
/*******************************************************************************
function:
Common register initialization
*******************************************************************************/
static void LCD_InitReg(void)
{
id = LCD_Read_Id();
if(LCD_2_8 == id){
LCD_WriteReg(0x11);
Driver_Delay_ms(100);
LCD_WriteReg(0x36);
LCD_WriteData(0x00);
LCD_WriteReg(0x3a);
LCD_WriteData(0x55);
LCD_WriteReg(0xb2);
LCD_WriteData(0x0c);
LCD_WriteData(0x0c);
LCD_WriteData(0x00);
LCD_WriteData(0x33);
LCD_WriteData(0x33);
LCD_WriteReg(0xb7);
LCD_WriteData(0x35);
LCD_WriteReg(0xbb);
LCD_WriteData(0x28);
LCD_WriteReg(0xc0);
LCD_WriteData(0x3c);
LCD_WriteReg(0xc2);
LCD_WriteData(0x01);
LCD_WriteReg(0xc3);
LCD_WriteData(0x0b);
LCD_WriteReg(0xc4);
LCD_WriteData(0x20);
LCD_WriteReg(0xc6);
LCD_WriteData(0x0f);
LCD_WriteReg(0xD0);
LCD_WriteData(0xa4);
LCD_WriteData(0xa1);
LCD_WriteReg(0xe0);
LCD_WriteData(0xd0);
LCD_WriteData(0x01);
LCD_WriteData(0x08);
LCD_WriteData(0x0f);
LCD_WriteData(0x11);
LCD_WriteData(0x2a);
LCD_WriteData(0x36);
LCD_WriteData(0x55);
LCD_WriteData(0x44);
LCD_WriteData(0x3a);
LCD_WriteData(0x0b);
LCD_WriteData(0x06);
LCD_WriteData(0x11);
LCD_WriteData(0x20);
LCD_WriteReg(0xe1);
LCD_WriteData(0xd0);
LCD_WriteData(0x02);
LCD_WriteData(0x07);
LCD_WriteData(0x0a);
LCD_WriteData(0x0b);
LCD_WriteData(0x18);
LCD_WriteData(0x34);
LCD_WriteData(0x43);
LCD_WriteData(0x4a);
LCD_WriteData(0x2b);
LCD_WriteData(0x1b);
LCD_WriteData(0x1c);
LCD_WriteData(0x22);
LCD_WriteData(0x1f);
LCD_WriteReg(0x55);
LCD_WriteData(0xB0);
LCD_WriteReg(0x29);
}else{
LCD_WriteReg(0x21);
LCD_WriteReg(0xC2); //Normal mode, increase can change the display quality, while increasing power consumption
LCD_WriteData(0x33);
LCD_WriteReg(0XC5);
LCD_WriteData(0x00);
LCD_WriteData(0x1e);//VCM_REG[7:0]. <=0X80.
LCD_WriteData(0x80);
LCD_WriteReg(0xB1);//Sets the frame frequency of full color normal mode
LCD_WriteData(0xB0);//0XB0 =70HZ, <=0XB0.0xA0=62HZ
LCD_WriteReg(0x36);
LCD_WriteData(0x28); //2 DOT FRAME MODE,F<=70HZ.
LCD_WriteReg(0XE0);
LCD_WriteData(0x0);
LCD_WriteData(0x13);
LCD_WriteData(0x18);
LCD_WriteData(0x04);
LCD_WriteData(0x0F);
LCD_WriteData(0x06);
LCD_WriteData(0x3a);
LCD_WriteData(0x56);
LCD_WriteData(0x4d);
LCD_WriteData(0x03);
LCD_WriteData(0x0a);
LCD_WriteData(0x06);
LCD_WriteData(0x30);
LCD_WriteData(0x3e);
LCD_WriteData(0x0f);
LCD_WriteReg(0XE1);
LCD_WriteData(0x0);
LCD_WriteData(0x13);
LCD_WriteData(0x18);
LCD_WriteData(0x01);
LCD_WriteData(0x11);
LCD_WriteData(0x06);
LCD_WriteData(0x38);
LCD_WriteData(0x34);
LCD_WriteData(0x4d);
LCD_WriteData(0x06);
LCD_WriteData(0x0d);
LCD_WriteData(0x0b);
LCD_WriteData(0x31);
LCD_WriteData(0x37);
LCD_WriteData(0x0f);
LCD_WriteReg(0X3A); //Set Interface Pixel Format
LCD_WriteData(0x55);
LCD_WriteReg(0x11);//sleep out
Driver_Delay_ms(120);
LCD_WriteReg(0x29);//Turn on the LCD display
}
}
/********************************************************************************
function: Set the display scan and color transfer modes
parameter:
Scan_dir : Scan direction
Colorchose : RGB or GBR color format
********************************************************************************/
void LCD_SetGramScanWay(LCD_SCAN_DIR Scan_dir)
{
uint16_t MemoryAccessReg_Data = 0; //addr:0x36
uint16_t DisFunReg_Data = 0; //addr:0xB6
if(LCD_2_8 == id){
/* it will support later */
//Pico-ResTouch-LCD-2.8
// switch(Scan_dir){
// case L2R_U2D:
// /* Memory access control: MY = 0, MX = 0, MV = 0, ML = 0 RGB = 0 MH = 0 NN = 0 NN = 0*/
// MemoryAccessReg_Data = 0x00;
// break;
// case L2R_D2U:
// /* Memory access control: MY = 0, MX = 0, MV = 0, ML = 1 RGB = 0 MH = 0 NN = 0 NN = 0*/
// MemoryAccessReg_Data = 0x10;
// break;
// case R2L_U2D:
// /* Memory access control: MY = 0, MX = 0, MV = 0, ML = 0 RGB = 0 MH = 1 NN = 0 NN = 0*/
// MemoryAccessReg_Data = 0x04;
// break;
// case R2L_D2U:
// /* Memory access control: MY = 0, MX = 0, MV = 0, ML = 1 RGB = 0 MH = 1 NN = 0 NN = 0*/
// MemoryAccessReg_Data = 0x14;
// break;
// case U2D_L2R: //0X2
// /* Memory access control: MY = 1, MX = 0, MV = 1, ML = 0 RGB = 0 MH = 0 NN = 0 NN = 0*/
// MemoryAccessReg_Data = 0xA0;
// break;
// case U2D_R2L: //0X6
// /* Memory access control: MY = 1, MX = 0, MV = 1, ML = 0 RGB = 0 MH = 1 NN = 0 NN = 0*/
// MemoryAccessReg_Data = 0xA4;
// break;
// case D2U_L2R: //0XA
// /* Memory access control: MY = 1, MX = 0, MV = 1, ML = 1 RGB = 0 MH = 0 NN = 0 NN = 0*/
// MemoryAccessReg_Data = 0xB0;
// break;
// case D2U_R2L: //0XE
// /* Memory access control: MY = 1, MX = 0, MV = 1, ML = 1 RGB = 0 MH = 1 NN = 0 NN = 0*/
// MemoryAccessReg_Data = 0xB4;
// break;
// }
sLCD_DIS.LCD_Scan_Dir = Scan_dir;
//Get GRAM and LCD width and height
//240*320,vertical default
// if(Scan_dir == L2R_U2D || Scan_dir == L2R_D2U || Scan_dir == R2L_U2D || Scan_dir == R2L_D2U) {
// sLCD_DIS.LCD_Dis_Column = LCD_2_8_WIDTH ;
// sLCD_DIS.LCD_Dis_Page = LCD_2_8_HEIGHT;
// } else {
// sLCD_DIS.LCD_Dis_Column = LCD_2_8_HEIGHT;
// sLCD_DIS.LCD_Dis_Page = LCD_2_8_WIDTH ;
// }
sLCD_DIS.LCD_Dis_Column = LCD_2_8_WIDTH ;
sLCD_DIS.LCD_Dis_Page = LCD_2_8_HEIGHT;
LCD_WriteReg(0x36);
// LCD_WriteData(MemoryAccessReg_Data);
LCD_WriteData(0x00);
}else{
//Pico-ResTouch-LCD-3.5
// Gets the scan direction of GRAM
switch (Scan_dir) {
case L2R_U2D:
/* Memory access control: MY = 0, MX = 0, MV = 0, ML = 0 */
/* Display Function control: NN = 0, GS = 0, SS = 1, SM = 0 */
MemoryAccessReg_Data = 0x08;
DisFunReg_Data = 0x22;
break;
case L2R_D2U:
/* Memory access control: MY = 0, MX = 0, MV = 0, ML = 0 */
/* Display Function control: NN = 0, GS = 1, SS = 1, SM = 0 */
MemoryAccessReg_Data = 0x08;
DisFunReg_Data = 0x62;
break;
case R2L_U2D:
/* Memory access control: MY = 0, MX = 0, MV = 0, ML = 0 */
/* Display Function control: NN = 0, GS = 0, SS = 0, SM = 0 */
MemoryAccessReg_Data = 0x08;
DisFunReg_Data = 0x02;
break;
case R2L_D2U:
/* Memory access control: MY = 0, MX = 0, MV = 0, ML = 0 */
/* Display Function control: NN = 0, GS = 1, SS = 0, SM = 0 */
MemoryAccessReg_Data = 0x08;
DisFunReg_Data = 0x42;
break;
case U2D_L2R: //0X2
/* Memory access control: MY = 0, MX = 0, MV = 1, ML = 0 X-Y Exchange*/
/* Display Function control: NN = 0, GS = 0, SS = 1, SM = 0 */
MemoryAccessReg_Data = 0x28;
DisFunReg_Data = 0x22;
break;
case U2D_R2L: //0X6
/* Memory access control: MY = 0, MX = 0, MV = 1, ML = 0 X-Y Exchange*/
/* Display Function control: NN = 0, GS = 0, SS = 0, SM = 0 */
MemoryAccessReg_Data = 0x28;
DisFunReg_Data = 0x02;
break;
case D2U_L2R: //0XA
/* Memory access control: MY = 0, MX = 0, MV = 1, ML = 0 X-Y Exchange*/
/* Display Function control: NN = 0, GS = 1, SS = 1, SM = 0 */
MemoryAccessReg_Data = 0x28;
DisFunReg_Data = 0x62;
break;
case D2U_R2L: //0XE
/* Memory access control: MY = 0, MX = 0, MV = 1, ML = 0 X-Y Exchange*/
/* Display Function control: NN = 0, GS = 1, SS = 0, SM = 0 */
MemoryAccessReg_Data = 0x28;
DisFunReg_Data = 0x42;
break;
}
//Get the screen scan direction
sLCD_DIS.LCD_Scan_Dir = Scan_dir;
//Get GRAM and LCD width and height
//480*320,horizontal default
if(Scan_dir == L2R_U2D || Scan_dir == L2R_D2U || Scan_dir == R2L_U2D || Scan_dir == R2L_D2U) {
sLCD_DIS.LCD_Dis_Column = LCD_3_5_HEIGHT ;
sLCD_DIS.LCD_Dis_Page = LCD_3_5_WIDTH ;
} else {
sLCD_DIS.LCD_Dis_Column = LCD_3_5_WIDTH ;
sLCD_DIS.LCD_Dis_Page = LCD_3_5_HEIGHT ;
}
// Set the read / write scan direction of the frame memory
LCD_WriteReg(0xB6);
LCD_WriteData(0X00);
LCD_WriteData(DisFunReg_Data);
LCD_WriteReg(0x36);
LCD_WriteData(MemoryAccessReg_Data);
}
}
/********************************************************************************
function:
initialization
********************************************************************************/
void LCD_Init(LCD_SCAN_DIR LCD_ScanDir, uint16_t LCD_BLval)
{
LCD_Reset();//Hardware reset
LCD_InitReg();//Set the initialization register
if(LCD_BLval > 1000)
LCD_BLval = 1000;
LCD_SetBackLight(LCD_BLval);
LCD_SetGramScanWay(LCD_ScanDir);//Set the display scan and color transfer modes
Driver_Delay_ms(200);
}
/********************************************************************************
function: Sets the start position and size of the display area
parameter:
Xstart : X direction Start coordinates
Ystart : Y direction Start coordinates
Xend : X direction end coordinates
Yend : Y direction end coordinates
********************************************************************************/
void LCD_SetWindow(POINT Xstart, POINT Ystart, POINT Xend, POINT Yend)
{
//set the X coordinates
LCD_WriteReg(0x2A);
LCD_WriteData(Xstart >> 8); //Set the horizontal starting point to the high octet
LCD_WriteData(Xstart & 0xff); //Set the horizontal starting point to the low octet
LCD_WriteData((Xend - 1) >> 8); //Set the horizontal end to the high octet
LCD_WriteData((Xend - 1) & 0xff); //Set the horizontal end to the low octet
//set the Y coordinates
LCD_WriteReg(0x2B);
LCD_WriteData(Ystart >> 8);
LCD_WriteData(Ystart & 0xff );
LCD_WriteData((Yend - 1) >> 8);
LCD_WriteData((Yend - 1) & 0xff);
LCD_WriteReg(0x2C);
}
/********************************************************************************
function: Set the display point (Xpoint, Ypoint)
parameter:
xStart : X direction Start coordinates
xEnd : X direction end coordinates
********************************************************************************/
void LCD_SetCursor(POINT Xpoint, POINT Ypoint)
{
LCD_SetWindow(Xpoint, Ypoint, Xpoint, Ypoint);
}
/********************************************************************************
function: Set show color
parameter:
Color : Set show color,16-bit depth
********************************************************************************/
void LCD_SetColor(COLOR Color , POINT Xpoint, POINT Ypoint)
{
LCD_Write_AllData(Color , (uint32_t)Xpoint * (uint32_t)Ypoint);
}
/********************************************************************************
function: Point (Xpoint, Ypoint) Fill the color
parameter:
Xpoint : The x coordinate of the point
Ypoint : The y coordinate of the point
Color : Set the color
********************************************************************************/
void LCD_SetPointlColor( POINT Xpoint, POINT Ypoint, COLOR Color)
{
if ((Xpoint <= sLCD_DIS.LCD_Dis_Column) && (Ypoint <= sLCD_DIS.LCD_Dis_Page)) {
LCD_SetCursor (Xpoint, Ypoint);
LCD_SetColor(Color, 1, 1);
}
}
/********************************************************************************
function: Fill the area with the color
parameter:
Xstart : Start point x coordinate
Ystart : Start point y coordinate
Xend : End point coordinates
Yend : End point coordinates
Color : Set the color
********************************************************************************/
void LCD_SetArealColor(POINT Xstart, POINT Ystart, POINT Xend, POINT Yend, COLOR Color)
{
if((Xend > Xstart) && (Yend > Ystart)) {
LCD_SetWindow(Xstart , Ystart , Xend , Yend );
LCD_SetColor ( Color , Xend - Xstart, Yend - Ystart);
}
}
/********************************************************************************
function:
Clear screen
********************************************************************************/
void LCD_Clear(COLOR Color)
{
LCD_SetArealColor(0, 0, sLCD_DIS.LCD_Dis_Column , sLCD_DIS.LCD_Dis_Page , Color);
}
uint8_t LCD_Read_Id(void)
{
uint8_t reg = 0xDC;
uint8_t tx_val = 0x00;
uint8_t rx_val;
DEV_Digital_Write(LCD_CS_PIN, 0);
DEV_Digital_Write(LCD_DC_PIN, 0);
SPI4W_Write_Byte(reg);
spi_write_read_blocking(spi1,&tx_val,&rx_val,1);
DEV_Digital_Write(LCD_CS_PIN, 1);
return rx_val;
}
/*****************************************************************************
* | File : LCD_Driver.h
* | Author : Waveshare team
* | Function : ILI9486 Drive function
* | Info :
* Image scanning:
* Please use progressive scanning to generate images or fonts
*----------------
* | This version: V1.0
* | Date : 2018-01-11
* | Info : Basic version
*
******************************************************************************/
/**************************Intermediate driver layer**************************/
#ifndef __LCD_DRIVER_H
#define __LCD_DRIVER_H
#include "DEV_Config.h"
#define LCD_2_8 0x52
#define LCD_3_5 0x00
#define COLOR uint16_t //The variable type of the color (unsigned short)
#define POINT uint16_t //The type of coordinate (unsigned short)
#define LENGTH uint16_t //The type of coordinate (unsigned short)
/********************************************************************************
function:
Define the full screen height length of the display
********************************************************************************/
#define LCD_X_MAXPIXEL 480 //LCD width maximum memory
#define LCD_Y_MAXPIXEL 320 //LCD height maximum memory
#define LCD_X 0
#define LCD_Y 0
#define LCD_3_5_WIDTH (LCD_X_MAXPIXEL - 2 * LCD_X) //LCD width
#define LCD_3_5_HEIGHT LCD_Y_MAXPIXEL //LCD height
#define LCD_2_8_WIDTH 240 //LCD width
#define LCD_2_8_HEIGHT 320
/********************************************************************************
function:
scanning method
********************************************************************************/
typedef enum {
L2R_U2D = 0, //The display interface is displayed , left to right, up to down
L2R_D2U ,
R2L_U2D ,
R2L_D2U ,
U2D_L2R ,
U2D_R2L ,
D2U_L2R ,
D2U_R2L ,
} LCD_SCAN_DIR;
#define SCAN_DIR_DFT D2U_L2R //Default scan direction = L2R_U2D
/********************************************************************************
function:
Defines the total number of rows in the display area
********************************************************************************/
typedef struct {
LENGTH LCD_Dis_Column; //COLUMN
LENGTH LCD_Dis_Page; //PAGE
LCD_SCAN_DIR LCD_Scan_Dir;
POINT LCD_X_Adjust; //LCD x actual display position calibration
POINT LCD_Y_Adjust; //LCD y actual display position calibration
} LCD_DIS;
/********************************************************************************
function:
Macro definition variable name
********************************************************************************/
void LCD_Init(LCD_SCAN_DIR LCD_ScanDir, uint16_t LCD_BLval);
void LCD_SetGramScanWay(LCD_SCAN_DIR Scan_dir);
void LCD_WriteReg(uint8_t Reg);
void LCD_WriteData(uint16_t Data);
void LCD_SetWindow(POINT Xstart, POINT Ystart, POINT Xend, POINT Yend);
void LCD_SetCursor(POINT Xpoint, POINT Ypoint);
void LCD_SetColor(COLOR Color ,POINT Xpoint, POINT Ypoint);
void LCD_SetPointlColor(POINT Xpoint, POINT Ypoint, COLOR Color);
void LCD_SetArealColor(POINT Xstart, POINT Ystart, POINT Xend, POINT Yend,COLOR Color);
void LCD_Clear(COLOR Color);
uint8_t LCD_Read_Id(void);
void LCD_SetBackLight(uint16_t value);
#endif
/*****************************************************************************
* | File : LCD_GUI.c
* | Author : Waveshare team
* | Function : Achieve drawing: draw points, lines, boxes, circles and
* their size, solid dotted line, solid rectangle hollow
* rectangle, solid circle hollow circle.
* | Info :
* Achieve display characters: Display a single character, string, number
* Achieve time display: adaptive size display time minutes and seconds
*----------------
* | This version: V1.0
* | Date : 2017-08-16
* | Info : Basic version
*
******************************************************************************/
#include "LCD_GUI.h"
extern LCD_DIS sLCD_DIS;
extern uint8_t id;
/******************************************************************************
function: Coordinate conversion
******************************************************************************/
void GUI_Swop(POINT Point1, POINT Point2)
{
POINT Temp;
Temp = Point1;
Point1 = Point2;
Point2 = Temp;
}
/******************************************************************************
function: Coordinate conversion
******************************************************************************/
void GUI_Clear(COLOR Color)
{
LCD_Clear(Color);
}
/******************************************************************************
function: Draw Point(Xpoint, Ypoint) Fill the color
parameter:
Xpoint : The x coordinate of the point
Ypoint : The y coordinate of the point
Color : Set color
Dot_Pixel : point size
******************************************************************************/
void GUI_DrawPoint(POINT Xpoint, POINT Ypoint, COLOR Color,
DOT_PIXEL Dot_Pixel, DOT_STYLE DOT_STYLE)
{
if(Xpoint > sLCD_DIS.LCD_Dis_Column || Ypoint > sLCD_DIS.LCD_Dis_Page) {
//DEBUG("GUI_DrawPoint Input exceeds the normal display range\r\n");
return;
}
uint16_t XDir_Num , YDir_Num;
if(DOT_STYLE == DOT_STYLE_DFT) {
for(XDir_Num = 0; XDir_Num < 2 * Dot_Pixel - 1; XDir_Num++) {
for(YDir_Num = 0; YDir_Num < 2 * Dot_Pixel - 1; YDir_Num++) {
LCD_SetPointlColor(Xpoint + XDir_Num - Dot_Pixel, Ypoint + YDir_Num - Dot_Pixel, Color);
}
}
} else {
for(XDir_Num = 0; XDir_Num < Dot_Pixel; XDir_Num++) {
for(YDir_Num = 0; YDir_Num < Dot_Pixel; YDir_Num++) {
LCD_SetPointlColor(Xpoint + XDir_Num - 1, Ypoint + YDir_Num - 1, Color);
}
}
}
}
/******************************************************************************
function: Draw a line of arbitrary slope
parameter:
Xstart :Starting x point coordinates
Ystart :Starting x point coordinates
Xend :End point x coordinate
Yend :End point y coordinate
Color :The color of the line segment
******************************************************************************/
void GUI_DrawLine(POINT Xstart, POINT Ystart, POINT Xend, POINT Yend,
COLOR Color, LINE_STYLE Line_Style, DOT_PIXEL Dot_Pixel)
{
if(Xstart > sLCD_DIS.LCD_Dis_Column || Ystart > sLCD_DIS.LCD_Dis_Page ||
Xend > sLCD_DIS.LCD_Dis_Column || Yend > sLCD_DIS.LCD_Dis_Page) {
//DEBUG("GUI_DrawLine Input exceeds the normal display range\r\n");
return;
}
if(Xstart > Xend)
GUI_Swop(Xstart, Xend);
if(Ystart > Yend)
GUI_Swop(Ystart, Yend);
POINT Xpoint = Xstart;
POINT Ypoint = Ystart;
int32_t dx = (int32_t)Xend - (int32_t)Xstart >= 0 ? Xend - Xstart : Xstart - Xend;
int32_t dy = (int32_t)Yend - (int32_t)Ystart <= 0 ? Yend - Ystart : Ystart - Yend;
// Increment direction, 1 is positive, -1 is counter;
int32_t XAddway = Xstart < Xend ? 1 : -1;
int32_t YAddway = Ystart < Yend ? 1 : -1;
//Cumulative error
int32_t Esp = dx + dy;
int8_t Line_Style_Temp = 0;
for(;;) {
Line_Style_Temp++;
//Painted dotted line, 2 point is really virtual
if(Line_Style == LINE_DOTTED && Line_Style_Temp % 3 == 0) {
//DEBUG("LINE_DOTTED\r\n");
GUI_DrawPoint(Xpoint, Ypoint, LCD_BACKGROUND, Dot_Pixel, DOT_STYLE_DFT);
Line_Style_Temp = 0;
} else {
GUI_DrawPoint(Xpoint, Ypoint, Color, Dot_Pixel, DOT_STYLE_DFT);
}
if(2 * Esp >= dy) {
if(Xpoint == Xend) break;
Esp += dy;
Xpoint += XAddway;
}
if(2 * Esp <= dx) {
if(Ypoint == Yend) break;
Esp += dx;
Ypoint += YAddway;
}
}
}
/******************************************************************************
function: Draw a rectangle
parameter:
Xstart :Rectangular Starting x point coordinates
Ystart :Rectangular Starting x point coordinates
Xend :Rectangular End point x coordinate
Yend :Rectangular End point y coordinate
Color :The color of the Rectangular segment
Filled : Whether it is filled--- 1 solid 0:empty
******************************************************************************/
void GUI_DrawRectangle(POINT Xstart, POINT Ystart, POINT Xend, POINT Yend,
COLOR Color, DRAW_FILL Filled, DOT_PIXEL Dot_Pixel)
{
if(Xstart > sLCD_DIS.LCD_Dis_Column || Ystart > sLCD_DIS.LCD_Dis_Page ||
Xend > sLCD_DIS.LCD_Dis_Column || Yend > sLCD_DIS.LCD_Dis_Page) {
//DEBUG("Input exceeds the normal display range\r\n");
return;
}
if(Xstart > Xend)
GUI_Swop(Xstart, Xend);
if(Ystart > Yend)
GUI_Swop(Ystart, Yend);
if(Filled ) {
#if LOW_Speed_Show
POINT Ypoint;
for(Ypoint = Ystart; Ypoint < Yend; Ypoint++) {
GUI_DrawLine(Xstart, Ypoint, Xend, Ypoint, Color , LINE_SOLID, Dot_Pixel);
}
#elif HIGH_Speed_Show
LCD_SetArealColor( Xstart, Ystart, Xend, Yend, Color);
#endif
} else {
GUI_DrawLine(Xstart, Ystart, Xend, Ystart, Color , LINE_SOLID, Dot_Pixel);
GUI_DrawLine(Xstart, Ystart, Xstart, Yend, Color , LINE_SOLID, Dot_Pixel);
GUI_DrawLine(Xend, Yend, Xend, Ystart, Color , LINE_SOLID, Dot_Pixel);
GUI_DrawLine(Xend, Yend, Xstart, Yend, Color , LINE_SOLID, Dot_Pixel);
}
}
/******************************************************************************
function: Use the 8-point method to draw a circle of the
specified size at the specified position.
parameter:
X_Center :Center X coordinate
Y_Center :Center Y coordinate
Radius :circle Radius
Color :The color of the :circle segment
Filled : Whether it is filled: 1 filling 0:Do not
******************************************************************************/
void GUI_DrawCircle(POINT X_Center, POINT Y_Center, LENGTH Radius,
COLOR Color, DRAW_FILL Draw_Fill , DOT_PIXEL Dot_Pixel)
{
if(X_Center > sLCD_DIS.LCD_Dis_Column || Y_Center >= sLCD_DIS.LCD_Dis_Page) {
//DEBUG("GUI_DrawCircle Input exceeds the normal display range\r\n");
return;
}
//Draw a circle from(0, R) as a starting point
int16_t XCurrent, YCurrent;
XCurrent = 0;
YCurrent = Radius;
//Cumulative error,judge the next point of the logo
int16_t Esp = 3 - (Radius << 1 );
int16_t sCountY;
if(Draw_Fill == DRAW_FULL) {
while(XCurrent <= YCurrent ) { //Realistic circles
for(sCountY = XCurrent; sCountY <= YCurrent; sCountY ++ ) {
GUI_DrawPoint(X_Center + XCurrent, Y_Center + sCountY, Color, DOT_PIXEL_DFT, DOT_STYLE_DFT );//1
GUI_DrawPoint(X_Center - XCurrent, Y_Center + sCountY, Color, DOT_PIXEL_DFT, DOT_STYLE_DFT );//2
GUI_DrawPoint(X_Center - sCountY, Y_Center + XCurrent, Color, DOT_PIXEL_DFT, DOT_STYLE_DFT );//3
GUI_DrawPoint(X_Center - sCountY, Y_Center - XCurrent, Color, DOT_PIXEL_DFT, DOT_STYLE_DFT );//4
GUI_DrawPoint(X_Center - XCurrent, Y_Center - sCountY, Color, DOT_PIXEL_DFT, DOT_STYLE_DFT );//5
GUI_DrawPoint(X_Center + XCurrent, Y_Center - sCountY, Color, DOT_PIXEL_DFT, DOT_STYLE_DFT );//6
GUI_DrawPoint(X_Center + sCountY, Y_Center - XCurrent, Color, DOT_PIXEL_DFT, DOT_STYLE_DFT );//7
GUI_DrawPoint(X_Center + sCountY, Y_Center + XCurrent, Color, DOT_PIXEL_DFT, DOT_STYLE_DFT );
}
if(Esp < 0 )
Esp += 4 * XCurrent + 6;
else {
Esp += 10 + 4 * (XCurrent - YCurrent );
YCurrent --;
}
XCurrent ++;
}
} else { //Draw a hollow circle
while(XCurrent <= YCurrent ) {
GUI_DrawPoint(X_Center + XCurrent, Y_Center + YCurrent, Color, Dot_Pixel, DOT_STYLE_DFT );//1
GUI_DrawPoint(X_Center - XCurrent, Y_Center + YCurrent, Color, Dot_Pixel, DOT_STYLE_DFT );//2
GUI_DrawPoint(X_Center - YCurrent, Y_Center + XCurrent, Color, Dot_Pixel, DOT_STYLE_DFT );//3
GUI_DrawPoint(X_Center - YCurrent, Y_Center - XCurrent, Color, Dot_Pixel, DOT_STYLE_DFT );//4
GUI_DrawPoint(X_Center - XCurrent, Y_Center - YCurrent, Color, Dot_Pixel, DOT_STYLE_DFT );//5
GUI_DrawPoint(X_Center + XCurrent, Y_Center - YCurrent, Color, Dot_Pixel, DOT_STYLE_DFT );//6
GUI_DrawPoint(X_Center + YCurrent, Y_Center - XCurrent, Color, Dot_Pixel, DOT_STYLE_DFT );//7
GUI_DrawPoint(X_Center + YCurrent, Y_Center + XCurrent, Color, Dot_Pixel, DOT_STYLE_DFT );//0
if(Esp < 0 )
Esp += 4 * XCurrent + 6;
else {
Esp += 10 + 4 * (XCurrent - YCurrent );
YCurrent --;
}
XCurrent ++;
}
}
}
/******************************************************************************
function: Show English characters
parameter:
Xpoint :X coordinate
Ypoint :Y coordinate
Acsii_Char :To display the English characters
Font :A structure pointer that displays a character size
Color_Background : Select the background color of the English character
Color_Foreground : Select the foreground color of the English character
******************************************************************************/
void GUI_DisChar(POINT Xpoint, POINT Ypoint, const char Acsii_Char,
sFONT* Font, COLOR Color_Background, COLOR Color_Foreground)
{
POINT Page, Column;
if(Xpoint > sLCD_DIS.LCD_Dis_Column || Ypoint > sLCD_DIS.LCD_Dis_Page) {
//DEBUG("GUI_DisChar Input exceeds the normal display range\r\n");
return;
}
uint32_t Char_Offset = (Acsii_Char - ' ') * Font->Height * (Font->Width / 8 + (Font->Width % 8 ? 1 : 0));
const unsigned char *ptr = &Font->table[Char_Offset];
for(Page = 0; Page < Font->Height; Page ++ ) {
for(Column = 0; Column < Font->Width; Column ++ ) {
//To determine whether the font background color and screen background color is consistent
if(FONT_BACKGROUND == Color_Background) { //this process is to speed up the scan
if(*ptr & (0x80 >> (Column % 8)))
GUI_DrawPoint(Xpoint + Column, Ypoint + Page, Color_Foreground, DOT_PIXEL_DFT, DOT_STYLE_DFT);
} else {
if(*ptr & (0x80 >> (Column % 8))) {
GUI_DrawPoint(Xpoint + Column, Ypoint + Page, Color_Foreground, DOT_PIXEL_DFT, DOT_STYLE_DFT);
} else {
GUI_DrawPoint(Xpoint + Column, Ypoint + Page, Color_Background, DOT_PIXEL_DFT, DOT_STYLE_DFT);
}
}
//One pixel is 8 bits
if(Column % 8 == 7)
ptr++;
}/* Write a line */
if(Font->Width % 8 != 0)
ptr++;
}/* Write all */
}
/******************************************************************************
function: Display the string
parameter:
Xstart :X coordinate
Ystart :Y coordinate
pString :The first address of the English string to be displayed
Font :A structure pointer that displays a character size
Color_Background : Select the background color of the English character
Color_Foreground : Select the foreground color of the English character
******************************************************************************/
void GUI_DisString_EN(POINT Xstart, POINT Ystart, const char * pString,
sFONT* Font, COLOR Color_Background, COLOR Color_Foreground )
{
POINT Xpoint = Xstart;
POINT Ypoint = Ystart;
if(Xstart > sLCD_DIS.LCD_Dis_Column || Ystart > sLCD_DIS.LCD_Dis_Page) {
//DEBUG("GUI_DisString_EN Input exceeds the normal display range\r\n");
return;
}
while(* pString != '\0') {
//if X direction filled , reposition to(Xstart,Ypoint),Ypoint is Y direction plus the height of the character
if((Xpoint + Font->Width ) > sLCD_DIS.LCD_Dis_Column ) {
Xpoint = Xstart;
Ypoint += Font->Height;
}
// If the Y direction is full, reposition to(Xstart, Ystart)
if((Ypoint + Font->Height ) > sLCD_DIS.LCD_Dis_Page ) {
Xpoint = Xstart;
Ypoint = Ystart;
}
GUI_DisChar(Xpoint, Ypoint, * pString, Font, Color_Background, Color_Foreground);
//The next character of the address
pString ++;
//The next word of the abscissa increases the font of the broadband
Xpoint += Font->Width;
}
}
/******************************************************************************
function: Display the string
parameter:
Xstart :X coordinate
Ystart : Y coordinate
Nummber : The number displayed
Font :A structure pointer that displays a character size
Color_Background : Select the background color of the English character
Color_Foreground : Select the foreground color of the English character
******************************************************************************/
#define ARRAY_LEN 255
void GUI_DisNum(POINT Xpoint, POINT Ypoint, int32_t Nummber,
sFONT* Font, COLOR Color_Background, COLOR Color_Foreground )
{
int16_t Num_Bit = 0, Str_Bit = 0;
uint8_t Str_Array[ARRAY_LEN] = {0}, Num_Array[ARRAY_LEN] = {0};
uint8_t *pStr = Str_Array;
if(Xpoint > sLCD_DIS.LCD_Dis_Column || Ypoint > sLCD_DIS.LCD_Dis_Page) {
//DEBUG("GUI_DisNum Input exceeds the normal display range\r\n");
return;
}
//Converts a number to a string
while(Nummber) {
Num_Array[Num_Bit] = Nummber % 10 + '0';
Num_Bit++;
Nummber /= 10;
}
//The string is inverted
while(Num_Bit > 0) {
Str_Array[Str_Bit] = Num_Array[Num_Bit - 1];
Str_Bit ++;
Num_Bit --;
}
//show
GUI_DisString_EN(Xpoint, Ypoint, (const char*)pStr, Font, Color_Background, Color_Foreground );
}
/******************************************************************************
function: Display the bit map,1 byte = 8bit = 8 points
parameter:
Xpoint :X coordinate
Ypoint : Y coordinate
pMap : Pointing to the picture
Width :Bitmap Width
Height : Bitmap Height
note:
This function is suitable for bitmap, because a 16-bit data accounted for 16 points
******************************************************************************/
void GUI_Disbitmap(POINT Xpoint, POINT Ypoint, const unsigned char *pMap,
POINT Width, POINT Height)
{
POINT i, j, byteWidth = (Width + 7) / 8;
for(j = 0; j < Height; j++) {
for(i = 0; i < Width; i ++) {
if(*(pMap + j * byteWidth + i / 8) & (128 >> (i & 7))) {
GUI_DrawPoint(Xpoint + i, Ypoint + j, WHITE, DOT_PIXEL_DFT, DOT_STYLE_DFT);
}
}
}
}
/******************************************************************************
function: Display the Gray map,1 byte = 8bit = 2 points
parameter:
Xpoint :X coordinate
Ypoint : Y coordinate
pMap : Pointing to the picture
Width :Bitmap Width
Height : Bitmap Height
note:
This function is suitable for bitmap, because a 4-bit data accounted for 1 points
Please use the Image2lcd generated array
******************************************************************************/
void GUI_DisGrayMap(POINT Xpoint, POINT Ypoint, const unsigned char *pBmp)
{
//Get the Map header Gray, width, height
char Gray;
Gray = *(pBmp + 1);
POINT Height, Width;
Width = (*(pBmp + 3) << 8) | (*(pBmp + 2));
Height = (*(pBmp + 5) << 8) | (*(pBmp + 4));
POINT i, j;
if(Gray == 0x04) { //Sixteen gray levels
pBmp = pBmp + 6;
for(j = 0; j < Height; j++)
for(i = 0; i < Width / 2; i++) {
GUI_DrawPoint(Xpoint + i * 2, Ypoint + j, ~(*pBmp >> 4), DOT_PIXEL_DFT, DOT_STYLE_DFT);
GUI_DrawPoint(Xpoint + i * 2 + 1, Ypoint + j, ~*pBmp , DOT_PIXEL_DFT, DOT_STYLE_DFT);
pBmp++;
}
} else {
//DEBUG("Does not support type\r\n");
return;
}
}
sFONT *GUI_GetFontSize(POINT Dx, POINT Dy)
{
sFONT *Font;
if (Dx > Font24.Width && Dy > Font24.Height) {
Font = &Font24;
} else if ((Dx > Font20.Width && Dx < Font24.Width) &&
(Dy > Font20.Height && Dy < Font24.Height)) {
Font = &Font20;
} else if ((Dx > Font16.Width && Dx < Font20.Width) &&
(Dy > Font16.Height && Dy < Font20.Height)) {
Font = &Font16;
} else if ((Dx > Font12.Width && Dx < Font16.Width) &&
(Dy > Font12.Height && Dy < Font16.Height)) {
Font = &Font12;
} else if ((Dx > Font8.Width && Dx < Font12.Width) &&
(Dy > Font8.Height && Dy < Font12.Height)) {
Font = &Font8;
} else {
//DEBUG("Please change the display area size, or add a larger font to modify\r\n");
}
return Font;
}
/******************************************************************************
function: According to the display area adaptive display time
parameter:
xStart : X direction Start coordinates
Ystart : Y direction Start coordinates
Xend : X direction end coordinates
Yend : Y direction end coordinates
pTime : Pointer to the definition of the structure
Color : Set show color
note:
******************************************************************************/
void GUI_Showtime(POINT Xstart, POINT Ystart, POINT Xend, POINT Yend,
DEV_TIME *pTime, COLOR Color)
{
uint8_t value[10] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
sFONT *Font;
//According to the display area adaptive font size
POINT Dx = (Xend - Xstart) / 7;//Determine the spacing between characters
POINT Dy = Yend - Ystart; //determine the font size
Font = GUI_GetFontSize(Dx, Dy);
if ((pTime->Sec % 10) < 10 && (pTime->Sec % 10) > 0) {
LCD_SetArealColor(Xstart + Dx * 6, Ystart, Xend, Yend, WHITE);// xx:xx:x0
} else {
if ((pTime->Sec / 10) < 6 && (pTime->Sec / 10) > 0) {
LCD_SetArealColor(Xstart + Dx * 5, Ystart, Xend, Yend, WHITE);// xx:xx:00
} else {//sec = 60
pTime->Min = pTime->Min + 1;
pTime->Sec = 0;
if ((pTime->Min % 10) < 10 && (pTime->Min % 10) > 0) {
LCD_SetArealColor(Xstart + Dx * 3 + Dx / 2, Ystart, Xend, Yend, WHITE);// xx:x0:00
} else {
if ((pTime->Min / 10) < 6 && (pTime->Min / 10) > 0) {
LCD_SetArealColor(Xstart + Dx * 2 + Dx / 2, Ystart, Xend, Yend, WHITE);// xx:00:00
} else {//min = 60
pTime->Hour = pTime->Hour + 1;
pTime->Min = 0;
if ((pTime->Hour % 10) < 4 && (pTime->Hour % 10) > 0 && pTime->Hour < 24) {// x0:00:00
LCD_SetArealColor(Xstart + Dx, Ystart, Xend, Yend, WHITE);
} else {
pTime->Hour = 0;
pTime->Min = 0;
pTime->Sec = 0;
LCD_SetArealColor(Xstart, Ystart, Xend, Yend, WHITE);// 00:00:00
}
}
}
}
}
//Write data into the cache
GUI_DisChar(Xstart , Ystart, value[pTime->Hour / 10], Font, FONT_BACKGROUND, Color);
GUI_DisChar(Xstart + Dx , Ystart, value[pTime->Hour % 10], Font, FONT_BACKGROUND, Color);
GUI_DisChar(Xstart + Dx + Dx / 4 + Dx / 2 , Ystart, ':' , Font, FONT_BACKGROUND, Color);
GUI_DisChar(Xstart + Dx * 2 + Dx / 2 , Ystart, value[pTime->Min / 10] , Font, FONT_BACKGROUND, Color);
GUI_DisChar(Xstart + Dx * 3 + Dx / 2 , Ystart, value[pTime->Min % 10] , Font, FONT_BACKGROUND, Color);
GUI_DisChar(Xstart + Dx * 4 + Dx / 2 - Dx / 4, Ystart, ':' , Font, FONT_BACKGROUND, Color);
GUI_DisChar(Xstart + Dx * 5 , Ystart, value[pTime->Sec / 10] , Font, FONT_BACKGROUND, Color);
GUI_DisChar(Xstart + Dx * 6 , Ystart, value[pTime->Sec % 10] , Font, FONT_BACKGROUND, Color);
}
/******************************************************************************
function: GUI_Show
note:
Clear,
Draw Line,
Draw Rectangle,
Draw Rings,
Draw Olympic Rings,
Display String,
Show Pic
******************************************************************************/
void GUI_Show(void)
{
//DEBUG("LCD_Dis_Column = %d\r\n", sLCD_DIS.LCD_Dis_Column);
//DEBUG("LCD_Dis_Page = %d\r\n", sLCD_DIS.LCD_Dis_Page);
if(LCD_2_8==id){
GUI_Clear(WHITE);
//DEBUG("Draw Line\r\n");
GUI_DrawLine(0, 10, sLCD_DIS.LCD_Dis_Column , 10, RED, LINE_SOLID, DOT_PIXEL_2X2);
GUI_DrawLine(0, 20, sLCD_DIS.LCD_Dis_Column , 20, RED, LINE_DOTTED, DOT_PIXEL_DFT);
GUI_DrawLine(0, sLCD_DIS.LCD_Dis_Page - 20, sLCD_DIS.LCD_Dis_Column , sLCD_DIS.LCD_Dis_Page - 20, RED, LINE_DOTTED, DOT_PIXEL_DFT);
GUI_DrawLine(0, sLCD_DIS.LCD_Dis_Page - 10, sLCD_DIS.LCD_Dis_Column , sLCD_DIS.LCD_Dis_Page - 10, RED, LINE_SOLID, DOT_PIXEL_2X2);
//DEBUG("Draw Rectangle\r\n");
GUI_DrawRectangle(10, 30, sLCD_DIS.LCD_Dis_Column - 10, sLCD_DIS.LCD_Dis_Page - 30, BLUE, DRAW_EMPTY, DOT_PIXEL_DFT);
GUI_DrawRectangle(20, 40, sLCD_DIS.LCD_Dis_Column - 20, 60, BLUE, DRAW_FULL, DOT_PIXEL_DFT);
//DEBUG("Draw Olympic Rings\r\n");
uint16_t Cx1 = 100, Cy1 = 260, Cr = 10;
uint16_t Cx2 = Cx1 + (2.5 * Cr), Cy2 = Cy1;
uint16_t Cx3 = Cx1 + (5 * Cr), Cy3 = Cy1;
uint16_t Cx4 = ( Cx1 + Cx2 ) / 2, Cy4 = Cy1 + Cr;
uint16_t Cx5 = ( Cx2 + Cx3 ) / 2, Cy5 = Cy1 + Cr;
GUI_DrawCircle( Cx1, Cy1, Cr, BLUE, DRAW_EMPTY, DOT_PIXEL_2X2);
GUI_DrawCircle( Cx2, Cy2, Cr, BLACK, DRAW_EMPTY, DOT_PIXEL_2X2);
GUI_DrawCircle( Cx3, Cy3, Cr, RED, DRAW_EMPTY, DOT_PIXEL_2X2);
GUI_DrawCircle( Cx4, Cy4, Cr, YELLOW, DRAW_EMPTY, DOT_PIXEL_2X2);
GUI_DrawCircle( Cx5, Cy5, Cr, GREEN, DRAW_EMPTY, DOT_PIXEL_2X2);
//DEBUG("Draw Realistic circles\r\n");
GUI_DrawCircle(50, 220, 30, CYAN, DRAW_FULL, DOT_PIXEL_DFT);
GUI_DrawCircle(sLCD_DIS.LCD_Dis_Column - 50, 220, 30, CYAN, DRAW_FULL, DOT_PIXEL_DFT);
//DEBUG("Display String\r\n");
GUI_DisString_EN(10, 120, "WaveShare Electronic", &Font16, LCD_BACKGROUND, BLUE);
GUI_DisString_EN(40, 150, "2.8inch TFTLCD", &Font16, RED, BLUE);
//DEBUG("Display Nummber\r\n");
GUI_DisNum(40, 170, 1234567890, &Font12, LCD_BACKGROUND, BLUE);
}else{
GUI_Clear(WHITE);
if(sLCD_DIS.LCD_Dis_Column > sLCD_DIS.LCD_Dis_Page) { //Horizontal screen display
//DEBUG("Draw Line\r\n");
GUI_DrawLine(0, 10, LCD_3_5_WIDTH, 10, RED, LINE_SOLID, DOT_PIXEL_2X2);
GUI_DrawLine(0, 20, LCD_3_5_WIDTH, 20, RED, LINE_DOTTED, DOT_PIXEL_DFT);
GUI_DrawLine(0, 300, LCD_3_5_WIDTH, 300, RED, LINE_DOTTED, DOT_PIXEL_DFT);
GUI_DrawLine(0, 310, LCD_3_5_WIDTH, 310, RED, LINE_SOLID, DOT_PIXEL_2X2);
//DEBUG("Draw Rectangle\r\n");
GUI_DrawRectangle(10, 30, sLCD_DIS.LCD_Dis_Column - 10, sLCD_DIS.LCD_Dis_Page - 30, BLUE, DRAW_EMPTY, DOT_PIXEL_DFT);
GUI_DrawRectangle(20, 40, sLCD_DIS.LCD_Dis_Column - 20, 60, BLUE, DRAW_FULL, DOT_PIXEL_DFT);
//DEBUG("Draw Olympic Rings\r\n");
uint16_t Cx1 = 190, Cy1 = 240, Cr = 20;
uint16_t Cx2 = Cx1 + (2.5 * Cr), Cy2 = Cy1;
uint16_t Cx3 = Cx1 + (5 * Cr), Cy3 = Cy1;
uint16_t Cx4 = ( Cx1 + Cx2 ) / 2, Cy4 = Cy1 + Cr;
uint16_t Cx5 = ( Cx2 + Cx3 ) / 2, Cy5 = Cy1 + Cr;
GUI_DrawCircle( Cx1, Cy1, Cr, BLUE, DRAW_EMPTY, DOT_PIXEL_2X2);
GUI_DrawCircle( Cx2, Cy2, Cr, BLACK, DRAW_EMPTY, DOT_PIXEL_2X2);
GUI_DrawCircle( Cx3, Cy3, Cr, RED, DRAW_EMPTY, DOT_PIXEL_2X2);
GUI_DrawCircle( Cx4, Cy4, Cr, YELLOW, DRAW_EMPTY, DOT_PIXEL_2X2);
GUI_DrawCircle( Cx5, Cy5, Cr, GREEN, DRAW_EMPTY, DOT_PIXEL_2X2);
//DEBUG("Draw Realistic circles\r\n");
GUI_DrawCircle(50, 250, 30, CYAN, DRAW_FULL, DOT_PIXEL_DFT);
GUI_DrawCircle(sLCD_DIS.LCD_Dis_Column - 50, 250, 30, CYAN, DRAW_FULL, DOT_PIXEL_DFT);
//DEBUG("Display String\r\n");
GUI_DisString_EN(80, 80, "WaveShare Electronic", &Font24, LCD_BACKGROUND, BLUE);
GUI_DisString_EN(80, 120, "3.5inch TFTLCD", &Font20, RED, BLUE);
//DEBUG("Display Nummber\r\n");
GUI_DisNum(80, 150, 1234567890, &Font16, LCD_BACKGROUND, BLUE);
} else { //Vertical screen display
//DEBUG("Draw Line\r\n");
GUI_DrawLine(0, 10, sLCD_DIS.LCD_Dis_Column , 10, RED, LINE_SOLID, DOT_PIXEL_2X2);
GUI_DrawLine(0, 20, sLCD_DIS.LCD_Dis_Column , 20, RED, LINE_DOTTED, DOT_PIXEL_DFT);
GUI_DrawLine(0, sLCD_DIS.LCD_Dis_Page - 20, sLCD_DIS.LCD_Dis_Column , sLCD_DIS.LCD_Dis_Page - 20, RED, LINE_DOTTED, DOT_PIXEL_DFT);
GUI_DrawLine(0, sLCD_DIS.LCD_Dis_Page - 10, sLCD_DIS.LCD_Dis_Column , sLCD_DIS.LCD_Dis_Page - 10, RED, LINE_SOLID, DOT_PIXEL_2X2);
//DEBUG("Draw Rectangle\r\n");
GUI_DrawRectangle(10, 30, sLCD_DIS.LCD_Dis_Column - 10, sLCD_DIS.LCD_Dis_Page - 30, BLUE, DRAW_EMPTY, DOT_PIXEL_DFT);
GUI_DrawRectangle(20, 40, sLCD_DIS.LCD_Dis_Column - 20, 60, BLUE, DRAW_FULL, DOT_PIXEL_DFT);
//DEBUG("Draw Olympic Rings\r\n");
uint16_t Cx1 = 120, Cy1 = 300, Cr = 20;
uint16_t Cx2 = Cx1 + (2.5 * Cr), Cy2 = Cy1;
uint16_t Cx3 = Cx1 + (5 * Cr), Cy3 = Cy1;
uint16_t Cx4 = ( Cx1 + Cx2 ) / 2, Cy4 = Cy1 + Cr;
uint16_t Cx5 = ( Cx2 + Cx3 ) / 2, Cy5 = Cy1 + Cr;
GUI_DrawCircle( Cx1, Cy1, Cr, BLUE, DRAW_EMPTY, DOT_PIXEL_2X2);
GUI_DrawCircle( Cx2, Cy2, Cr, BLACK, DRAW_EMPTY, DOT_PIXEL_2X2);
GUI_DrawCircle( Cx3, Cy3, Cr, RED, DRAW_EMPTY, DOT_PIXEL_2X2);
GUI_DrawCircle( Cx4, Cy4, Cr, YELLOW, DRAW_EMPTY, DOT_PIXEL_2X2);
GUI_DrawCircle( Cx5, Cy5, Cr, GREEN, DRAW_EMPTY, DOT_PIXEL_2X2);
//DEBUG("Draw Realistic circles\r\n");
GUI_DrawCircle(50, 400, 30, CYAN, DRAW_FULL, DOT_PIXEL_DFT);
GUI_DrawCircle(sLCD_DIS.LCD_Dis_Column - 50, 400, 30, CYAN, DRAW_FULL, DOT_PIXEL_DFT);
//DEBUG("Display String\r\n");
GUI_DisString_EN(40, 120, "WaveShare Electronic", &Font24, LCD_BACKGROUND, BLUE);
GUI_DisString_EN(40, 180, "3.5inch TFTLCD", &Font20, RED, BLUE);
//DEBUG("Display Nummber\r\n");
GUI_DisNum(40, 210, 1234567890, &Font16, LCD_BACKGROUND, BLUE);
}
}
}
/*****************************************************************************
* | File : LCD_GUI.h
* | Author : Waveshare team
* | Function : Achieve drawing: draw points, lines, boxes, circles and
* their size, solid dotted line, solid rectangle hollow
* rectangle, solid circle hollow circle.
* | Info :
* Achieve display characters: Display a single character, string, number
* Achieve time display: adaptive size display time minutes and seconds
*----------------
* | This version: V1.0
* | Date : 2017-08-16
* | Info : Basic version
*
******************************************************************************/
/****************************Upper application layer**************************/
#ifndef __LCD_GUI_H
#define __LCD_GUI_H
#include "LCD_Driver.h"
#include "fonts.h"
#define LOW_Speed_Show 0
#define HIGH_Speed_Show 1
/********************************************************************************
function:
dot pixel
********************************************************************************/
typedef enum {
DOT_PIXEL_1X1 = 1, // dot pixel 1 x 1
DOT_PIXEL_2X2 , // dot pixel 2 X 2
DOT_PIXEL_3X3 , // dot pixel 3 X 3
DOT_PIXEL_4X4 , // dot pixel 4 X 4
DOT_PIXEL_5X5 , // dot pixel 5 X 5
DOT_PIXEL_6X6 , // dot pixel 6 X 6
DOT_PIXEL_7X7 , // dot pixel 7 X 7
DOT_PIXEL_8X8 , // dot pixel 8 X 8
} DOT_PIXEL;
#define DOT_PIXEL_DFT DOT_PIXEL_1X1 //Default dot pilex
/********************************************************************************
function:
dot Fill style
********************************************************************************/
typedef enum {
DOT_FILL_AROUND = 1, // dot pixel 1 x 1
DOT_FILL_RIGHTUP , // dot pixel 2 X 2
} DOT_STYLE;
#define DOT_STYLE_DFT DOT_FILL_AROUND //Default dot pilex
/********************************************************************************
function:
solid line and dotted line
********************************************************************************/
typedef enum {
LINE_SOLID = 0,
LINE_DOTTED,
} LINE_STYLE;
/********************************************************************************
function:
DRAW Internal fill
********************************************************************************/
typedef enum {
DRAW_EMPTY = 0,
DRAW_FULL,
} DRAW_FILL;
/********************************************************************************
function:
time
********************************************************************************/
typedef struct {
uint16_t Year; //0000
uint8_t Month; //1 - 12
uint8_t Day; //1 - 30
uint8_t Hour; //0 - 23
uint8_t Min; //0 - 59
uint8_t Sec; //0 - 59
} DEV_TIME;
extern DEV_TIME sDev_time;
/********************************************************************************
function:
Defines commonly used colors for the display
********************************************************************************/
#define LCD_BACKGROUND WHITE //Default background color
#define FONT_BACKGROUND WHITE //Default font background color
#define FONT_FOREGROUND GRED //Default font foreground color
#define WHITE 0xFFFF
#define BLACK 0x0000
#define BLUE 0x001F
#define BRED 0XF81F
#define GRED 0XFFE0
#define GBLUE 0X07FF
#define RED 0xF800
#define MAGENTA 0xF81F
#define GREEN 0x07E0
#define CYAN 0x7FFF
#define YELLOW 0xFFE0
#define BROWN 0XBC40
#define BRRED 0XFC07
#define GRAY 0X8430
/********************************************************************************
function:
Macro definition variable name
********************************************************************************/
//Clear
void GUI_Clear(COLOR Color);
//Drawing
void GUI_DrawPoint(POINT Xpoint, POINT Ypoint, COLOR Color, DOT_PIXEL Dot_Pixel, DOT_STYLE Dot_FillWay);
void GUI_DrawLine(POINT Xstart, POINT Ystart, POINT Xend, POINT Yend, COLOR Color, LINE_STYLE Line_Style, DOT_PIXEL Dot_Pixel);
void GUI_DrawRectangle(POINT Xstart, POINT Ystart, POINT Xend, POINT Yend, COLOR Color, DRAW_FILL Filled , DOT_PIXEL Dot_Pixel );
void GUI_DrawCircle(POINT X_Center, POINT Y_Center, LENGTH Radius, COLOR Color, DRAW_FILL Draw_Fill , DOT_PIXEL Dot_Pixel );
//pic
void GUI_Disbitmap(POINT Xpoint, POINT Ypoint, const unsigned char *pMap, POINT Width, POINT Height);
void GUI_DisGrayMap(POINT Xpoint, POINT Ypoint, const unsigned char *pBmp);
//Display string
void GUI_DisChar(POINT Xstart, POINT Ystart, const char Acsii_Char, sFONT* Font, COLOR Color_Background, COLOR Color_Foreground);
void GUI_DisString_EN(POINT Xstart, POINT Ystart, const char * pString, sFONT* Font, COLOR Color_Background, COLOR Color_Foreground );
void GUI_DisNum(POINT Xpoint, POINT Ypoint, int32_t Nummber, sFONT* Font, COLOR Color_Background, COLOR Color_Foreground );
void GUI_Showtime(POINT Xstart, POINT Ystart, POINT Xend, POINT Yend, DEV_TIME *pTime, COLOR Color);
//show
void GUI_Show(void);
#endif
#include "LCD_Touch.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
extern LCD_DIS sLCD_DIS;
extern uint8_t id;
static TP_DEV sTP_DEV;
static TP_DRAW sTP_Draw;
// ---- Capture area (the black box) ------------------------------------------
#define BOX_X0 100
#define BOX_Y0 50
#define BOX_X1 380 // right edge (exclusive in our math)
#define BOX_Y1 290 // bottom edge (exclusive in our math)
#define BOX_W (BOX_X1 - BOX_X0) // 280
#define BOX_H (BOX_Y1 - BOX_Y0) // 240
// Live "shadow" of what has been drawn (0 = empty, 1 = drawn)
static uint8_t sDrawShadow[BOX_H][BOX_W];
// Snapshot taken when SAVE is pressed (this is what you'll write to SD later)
static uint8_t sSavedBitmap[BOX_H][BOX_W];
// Optional: quick accessor if you want to use these elsewhere
uint16_t TP_SavedWidth(void) { return BOX_W; }
uint16_t TP_SavedHeight(void) { return BOX_H; }
const uint8_t* TP_SavedData(void) { return &sSavedBitmap[0][0]; }
// Helper to record a pixel into the shadow buffer
static inline void Capture_SetPixel(uint16_t x, uint16_t y)
{
if (x >= BOX_X0 && x < BOX_X1 && y >= BOX_Y0 && y < BOX_Y1) {
sDrawShadow[y - BOX_Y0][x - BOX_X0] = 1;
}
}
static void TP_DumpBitmapToSerial(const uint8_t bmp[BOX_H][BOX_W])
{
// Build and print one line at a time (faster than putchar per pixel)
char line[BOX_W + 2]; // + '\n' + '\0'
line[BOX_W] = '\n';
line[BOX_W+1] = '\0';
for (uint16_t y = 0; y < BOX_H; ++y) {
for (uint16_t x = 0; x < BOX_W; ++x) {
line[x] = bmp[y][x] ? '1' : '0';
}
printf("%s", line); // prints 280 chars of 0/1, then newline
}
}
static uint16_t TP_Read_ADC(uint8_t CMD)
{
uint16_t Data = 0;
//A cycle of at least 400ns.
DEV_Digital_Write(TP_CS_PIN, 0);
SPI4W_Write_Byte(CMD);
Driver_Delay_us(200);
// dont write 0xff, it will block xpt2046
//Data = SPI4W_Read_Byte(0Xff);
Data = SPI4W_Read_Byte(0X00);
Data <<= 8; //7bit
Data |= SPI4W_Read_Byte(0X00);
//Data = SPI4W_Read_Byte(0Xff);
Data >>= 3; //5bit
DEV_Digital_Write(TP_CS_PIN, 1);
return Data;
}
#define READ_TIMES 5 //Number of readings
#define LOST_NUM 1 //Discard value
static uint16_t
TP_Read_ADC_Average(uint8_t Channel_Cmd)
{
uint8_t i, j;
uint16_t Read_Buff[READ_TIMES];
uint16_t Read_Sum = 0, Read_Temp = 0;
//LCD SPI speed = 3 MHz
spi_set_baudrate(SPI_PORT, 3000000);
//Read and save multiple samples
for (i = 0; i < READ_TIMES; i++)
{
Read_Buff[i] = TP_Read_ADC(Channel_Cmd);
Driver_Delay_us(200);
}
//LCD SPI speed = 18 MHz
spi_set_baudrate(SPI_PORT, 18000000);
//Sort from small to large
for (i = 0; i < READ_TIMES - 1; i++)
{
for (j = i + 1; j < READ_TIMES; j++)
{
if (Read_Buff[i] > Read_Buff[j])
{
Read_Temp = Read_Buff[i];
Read_Buff[i] = Read_Buff[j];
Read_Buff[j] = Read_Temp;
}
}
}
//Exclude the largest and the smallest
for (i = LOST_NUM; i < READ_TIMES - LOST_NUM; i++)
Read_Sum += Read_Buff[i];
//Averaging
Read_Temp = Read_Sum / (READ_TIMES - 2 * LOST_NUM);
return Read_Temp;
}
static void TP_Read_ADC_XY(uint16_t *pXCh_Adc, uint16_t *pYCh_Adc)
{
*pXCh_Adc = TP_Read_ADC_Average(0xD0);
*pYCh_Adc = TP_Read_ADC_Average(0x90);
}
#define ERR_RANGE 50 //tolerance scope
static bool TP_Read_TwiceADC(uint16_t *pXCh_Adc, uint16_t *pYCh_Adc)
{
uint16_t XCh_Adc1, YCh_Adc1, XCh_Adc2, YCh_Adc2;
//Read the ADC values Read the ADC values twice
TP_Read_ADC_XY(&XCh_Adc1, &YCh_Adc1);
Driver_Delay_us(10);
TP_Read_ADC_XY(&XCh_Adc2, &YCh_Adc2);
Driver_Delay_us(10);
//The ADC error used twice is greater than ERR_RANGE to take the average
if (((XCh_Adc2 <= XCh_Adc1 && XCh_Adc1 < XCh_Adc2 + ERR_RANGE) ||
(XCh_Adc1 <= XCh_Adc2 && XCh_Adc2 < XCh_Adc1 + ERR_RANGE)) &&
((YCh_Adc2 <= YCh_Adc1 && YCh_Adc1 < YCh_Adc2 + ERR_RANGE) ||
(YCh_Adc1 <= YCh_Adc2 && YCh_Adc2 < YCh_Adc1 + ERR_RANGE)))
{
*pXCh_Adc = (XCh_Adc1 + XCh_Adc2) / 2;
*pYCh_Adc = (YCh_Adc1 + YCh_Adc2) / 2;
return true;
}
//The ADC error used twice is less than ERR_RANGE returns failed
return false;
}
static uint8_t TP_Scan(uint8_t chCoordType)
{
//In X, Y coordinate measurement, IRQ is disabled and output is low
if (!DEV_Digital_Read(TP_IRQ_PIN))
{ //Press the button to press
//Read the physical coordinates
if (chCoordType)
{
TP_Read_TwiceADC(&sTP_DEV.Xpoint, &sTP_DEV.Ypoint);
//Read the screen coordinates
}
else if (TP_Read_TwiceADC(&sTP_DEV.Xpoint, &sTP_DEV.Ypoint))
{
if (LCD_2_8 == id)
{
sTP_Draw.Xpoint = sLCD_DIS.LCD_Dis_Column -
sTP_DEV.fXfac * sTP_DEV.Xpoint -
sTP_DEV.iXoff;
sTP_Draw.Ypoint = sLCD_DIS.LCD_Dis_Page -
sTP_DEV.fYfac * sTP_DEV.Ypoint -
sTP_DEV.iYoff;
}
else
{
//DEBUG("(Xad,Yad) = %d,%d\r\n",sTP_DEV.Xpoint,sTP_DEV.Ypoint);
if (sTP_DEV.TP_Scan_Dir == R2L_D2U)
{ //Converts the result to screen coordinates
sTP_Draw.Xpoint = sTP_DEV.fXfac * sTP_DEV.Xpoint +
sTP_DEV.iXoff;
sTP_Draw.Ypoint = sTP_DEV.fYfac * sTP_DEV.Ypoint +
sTP_DEV.iYoff;
}
else if (sTP_DEV.TP_Scan_Dir == L2R_U2D)
{
sTP_Draw.Xpoint = sLCD_DIS.LCD_Dis_Column -
sTP_DEV.fXfac * sTP_DEV.Xpoint -
sTP_DEV.iXoff;
sTP_Draw.Ypoint = sLCD_DIS.LCD_Dis_Page -
sTP_DEV.fYfac * sTP_DEV.Ypoint -
sTP_DEV.iYoff;
}
else if (sTP_DEV.TP_Scan_Dir == U2D_R2L)
{
sTP_Draw.Xpoint = sTP_DEV.fXfac * sTP_DEV.Ypoint +
sTP_DEV.iXoff;
sTP_Draw.Ypoint = sTP_DEV.fYfac * sTP_DEV.Xpoint +
sTP_DEV.iYoff;
}
else
{
sTP_Draw.Xpoint = sLCD_DIS.LCD_Dis_Column -
sTP_DEV.fXfac * sTP_DEV.Ypoint -
sTP_DEV.iXoff;
sTP_Draw.Ypoint = sLCD_DIS.LCD_Dis_Page -
sTP_DEV.fYfac * sTP_DEV.Xpoint -
sTP_DEV.iYoff;
}
// DEBUG("( x , y ) = %d,%d\r\n",sTP_Draw.Xpoint,sTP_Draw.Ypoint);
}
}
if (0 == (sTP_DEV.chStatus & TP_PRESS_DOWN))
{ //Not being pressed
sTP_DEV.chStatus = TP_PRESS_DOWN | TP_PRESSED;
sTP_DEV.Xpoint0 = sTP_DEV.Xpoint;
sTP_DEV.Ypoint0 = sTP_DEV.Ypoint;
}
}
else
{
if (sTP_DEV.chStatus & TP_PRESS_DOWN)
{ //0x80
sTP_DEV.chStatus &= ~(1 << 7); //0x00
}
else
{
sTP_DEV.Xpoint0 = 0;
sTP_DEV.Ypoint0 = 0;
sTP_DEV.Xpoint = 0xffff;
sTP_DEV.Ypoint = 0xffff;
}
}
return (sTP_DEV.chStatus & TP_PRESS_DOWN);
}
void TP_GetAdFac(void)
{
if (LCD_2_8 == id)
{
sTP_DEV.fXfac = 0.066626;
sTP_DEV.fYfac = 0.089779;
sTP_DEV.iXoff = -20;
sTP_DEV.iYoff = -34;
}
else
{
if (sTP_DEV.TP_Scan_Dir == D2U_L2R)
{ //SCAN_DIR_DFT = D2U_L2R
sTP_DEV.fXfac = -0.132443;
sTP_DEV.fYfac = 0.089997;
sTP_DEV.iXoff = 516;
sTP_DEV.iYoff = -22;
}
else if (sTP_DEV.TP_Scan_Dir == L2R_U2D)
{
sTP_DEV.fXfac = 0.089697;
sTP_DEV.fYfac = 0.134792;
sTP_DEV.iXoff = -21;
sTP_DEV.iYoff = -39;
}
else if (sTP_DEV.TP_Scan_Dir == R2L_D2U)
{
sTP_DEV.fXfac = 0.089915;
sTP_DEV.fYfac = 0.133178;
sTP_DEV.iXoff = -22;
sTP_DEV.iYoff = -38;
}
else if (sTP_DEV.TP_Scan_Dir == U2D_R2L)
{
sTP_DEV.fXfac = -0.132906;
sTP_DEV.fYfac = 0.087964;
sTP_DEV.iXoff = 517;
sTP_DEV.iYoff = -20;
}
else
{
LCD_Clear(LCD_BACKGROUND);
GUI_DisString_EN(0, 60, "Does not support touch-screen \
calibration in this direction",
&Font16, FONT_BACKGROUND, RED);
}
}
}
void TP_Dialog(void)
{
LCD_Clear(LCD_BACKGROUND);
GUI_DisString_EN(sLCD_DIS.LCD_Dis_Column - 60, 0,
"CLEAR", &Font16, BLACK, WHITE);
GUI_DisString_EN(sLCD_DIS.LCD_Dis_Column - 120, 0,
"SAVE", &Font16, BLACK, WHITE);
// Draw a box with black border
GUI_DrawRectangle(BOX_X0 , BOX_Y0 ,
BOX_X1, BOX_Y1,
BLACK, DRAW_EMPTY, DOT_PIXEL_2X2);
// NEW: also clear the shadow buffer that mirrors what's drawn
memset(sDrawShadow, 0, sizeof(sDrawShadow));
}
void TP_Save(void)
{
// Snapshot current drawing into the "saved" buffer
memcpy(sSavedBitmap, sDrawShadow, sizeof(sSavedBitmap));
// Optional on-screen / UART feedback
GUI_DisString_EN(sLCD_DIS.LCD_Dis_Column - 120, 24,
"SAVED", &Font16, BLACK, WHITE);
printf("TP_Save: captured %ux%u pixels from box [%u,%u]-[%u,%u]\r\n",
(unsigned)BOX_W, (unsigned)BOX_H,
(unsigned)BOX_X0, (unsigned)BOX_Y0,
(unsigned)BOX_X1, (unsigned)BOX_Y1);
// Print the full image as 0/1 so it "looks" like the drawing
// (top row first, left->right)
TP_DumpBitmapToSerial(sSavedBitmap);
}
void TP_DrawBoard(void)
{
// sTP_DEV.chStatus &= ~(1 << 6);
TP_Scan(0);
if (sTP_DEV.chStatus & TP_PRESS_DOWN)
{
spi_init(SPI_PORT, 10000000);
printf("horizontal x:%d,y:%d\n", sTP_Draw.Xpoint, sTP_Draw.Ypoint);
if (sTP_Draw.Xpoint > (sLCD_DIS.LCD_Dis_Column - 60) &&
sTP_Draw.Ypoint < 16)
{
TP_Dialog();
}
else if (sTP_Draw.Xpoint > (sLCD_DIS.LCD_Dis_Column - 120) &&
sTP_Draw.Xpoint < (sLCD_DIS.LCD_Dis_Column - 80) &&
sTP_Draw.Ypoint < 24)
{
TP_Save();
}
sTP_Draw.Color = BLACK;
if (sTP_Draw.Xpoint > 100 && sTP_Draw.Xpoint < 380 &&
sTP_Draw.Ypoint > 50 && sTP_Draw.Ypoint < 290)
{
GUI_DrawPoint(sTP_Draw.Xpoint, sTP_Draw.Ypoint,
sTP_Draw.Color, DOT_PIXEL_1X1, DOT_FILL_RIGHTUP);
Capture_SetPixel(sTP_Draw.Xpoint, sTP_Draw.Ypoint);
GUI_DrawPoint(sTP_Draw.Xpoint + 1, sTP_Draw.Ypoint,
sTP_Draw.Color, DOT_PIXEL_1X1, DOT_FILL_RIGHTUP);
Capture_SetPixel(sTP_Draw.Xpoint + 1, sTP_Draw.Ypoint);
GUI_DrawPoint(sTP_Draw.Xpoint, sTP_Draw.Ypoint + 1,
sTP_Draw.Color, DOT_PIXEL_1X1, DOT_FILL_RIGHTUP);
Capture_SetPixel(sTP_Draw.Xpoint, sTP_Draw.Ypoint + 1);
GUI_DrawPoint(sTP_Draw.Xpoint + 1, sTP_Draw.Ypoint + 1,
sTP_Draw.Color, DOT_PIXEL_1X1, DOT_FILL_RIGHTUP);
Capture_SetPixel(sTP_Draw.Xpoint + 1, sTP_Draw.Ypoint + 1);
// The DOT_PIXEL_2X2 point covers the same area; no extra capture needed
GUI_DrawPoint(sTP_Draw.Xpoint, sTP_Draw.Ypoint,
sTP_Draw.Color, DOT_PIXEL_2X2, DOT_FILL_RIGHTUP);
}
spi_init(SPI_PORT, 5000000);
}
SPI4W_Write_Byte(0xFF);
}
void TP_Init(LCD_SCAN_DIR Lcd_ScanDir)
{
DEV_Digital_Write(TP_CS_PIN, 1);
sTP_DEV.TP_Scan_Dir = Lcd_ScanDir;
TP_Read_ADC_XY(&sTP_DEV.Xpoint, &sTP_DEV.Ypoint);
}
#ifndef __LCD_TOUCH_H_
#define __LCD_TOUCH_H_
#include "DEV_Config.h"
#include "LCD_Driver.h"
#include "LCD_GUI.h"
#include <math.h>
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/float.h"
#define TP_PRESS_DOWN 0x80
#define TP_PRESSED 0x40
//Touch screen structure
typedef struct {
POINT Xpoint0;
POINT Ypoint0;
POINT Xpoint;
POINT Ypoint;
uint8_t chStatus;
uint8_t chType;
int16_t iXoff;
int16_t iYoff;
float fXfac;
float fYfac;
//Select the coordinates of the XPT2046 touch \
screen relative to what scan direction
LCD_SCAN_DIR TP_Scan_Dir;
}TP_DEV;
//Brush structure
typedef struct{
POINT Xpoint;
POINT Ypoint;
COLOR Color;
DOT_PIXEL DotPixel;
}TP_DRAW;
void TP_GetAdFac(void);
void TP_Adjust(void);
void TP_Dialog(void);
void TP_Save(void);
void TP_DrawBoard(void);
void TP_Init( LCD_SCAN_DIR Lcd_ScanDir );
#endif
#include "LCD_Driver.h"
#include "LCD_Touch.h"
#include "LCD_GUI.h"
#include "DEV_Config.h"
#include <stdio.h>
#include "hardware/watchdog.h"
#include "pico/stdlib.h"
int main(void)
{
uint8_t counter = 0;
System_Init();
LCD_SCAN_DIR lcd_scan_dir = SCAN_DIR_DFT;
LCD_Init(lcd_scan_dir,1000);
TP_Init(lcd_scan_dir);
LCD_SCAN_DIR bmp_scan_dir = D2U_R2L;
TP_GetAdFac();
TP_Dialog();
uint16_t cnt=0;
while(1){
for(cnt=1000;cnt>2;cnt--)
{
LCD_SetBackLight(1000);
TP_DrawBoard();
}
}
return 0;
}
\ No newline at end of file
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
endif ()
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
endif()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
)
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
GIT_SUBMODULES_RECURSE FALSE
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
else ()
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
endif ()
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})
build
!.vscode/*
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.2.0)
set(toolchainVersion 14_2_Rel1)
set(picotoolVersion 2.2.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD pico CACHE STRING "Board type")
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(main C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# --- Executable and microphone sources ---
add_executable(main
main.c
${CMAKE_CURRENT_LIST_DIR}/src/pdm_microphone.c
${CMAKE_CURRENT_LIST_DIR}/src/OpenPDM2PCM/OpenPDMFilter.c
)
# Generate PIO header for the mic
pico_generate_pio_header(main
${CMAKE_CURRENT_LIST_DIR}/src/pdm_microphone.pio
)
# Include directories (note the src/include path gets added)
target_include_directories(main PRIVATE
${CMAKE_CURRENT_LIST_DIR}/src
${CMAKE_CURRENT_LIST_DIR}/src/include
${CMAKE_CURRENT_LIST_DIR}/src/OpenPDM2PCM
${CMAKE_CURRENT_BINARY_DIR} # for staged "pico/pdm_microphone.h"
)
# --- Stage a virtual include so "pico/pdm_microphone.h" resolves ---
set(PDM_HDR_SRC ${CMAKE_CURRENT_LIST_DIR}/src/include/pico/pdm_microphone.h)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/pico/pdm_microphone.h
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/pico
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${PDM_HDR_SRC}
${CMAKE_CURRENT_BINARY_DIR}/pico/pdm_microphone.h
DEPENDS ${PDM_HDR_SRC}
COMMENT "Staging pico/pdm_microphone.h from src/include into build dir"
VERBATIM
)
add_custom_target(pdm_header ALL
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/pico/pdm_microphone.h
)
add_dependencies(main pdm_header)
# Link libraries
target_link_libraries(main PRIVATE
pico_stdlib
hardware_dma
hardware_pio
)
# Program metadata and I/O
pico_set_program_name(main "main")
pico_set_program_version(main "0.1")
pico_enable_stdio_uart(main 0)
pico_enable_stdio_usb(main 1)
# Create UF2, bin, etc.
pico_add_extra_outputs(main)
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include "stdlib.h"
#include "src/include/pico/pdm_microphone.h"
#include "tusb.h"
#define AUDIO_SAMPLE_RATE 16000
#define SAMPLE_BUFFER_SIZE 256
pdm_microphone_config config = {
// PDM ID
.pdm_id = 0,
.dma_irq = DMA_IRQ_0,
// PIO
.pio = pio0,
// GPIO pin for the PDM DAT signal
.gpio_data = 18,
// GPIO pin for the PDM CLK signal
.gpio_clk = 19,
// sample rate in Hz
.sample_rate = AUDIO_SAMPLE_RATE,
// number of samples to buffer
.sample_buffer_size = SAMPLE_BUFFER_SIZE,
};
pdm_mic_obj* pdm_mic;
int16_t sample_buffer[SAMPLE_BUFFER_SIZE];
volatile bool data_valid = false;
void on_pdm_samples_ready(uint8_t pdm_id) {
(void)pdm_id;
data_valid = true;
}
int main(void)
{
stdio_init_all();
while (!tud_cdc_connected()) {
sleep_ms(10);
}
printf("hello PDM microphone\n");
pdm_mic = pdm_microphone_init(&config);
if (!pdm_mic) {
printf("PDM microphone initialization failed!\n");
while (1) { tight_loop_contents(); }
}
printf("PDM microphone initialized\n");
pdm_microphone_set_samples_ready_handler(pdm_mic, on_pdm_samples_ready);
if (pdm_microphone_start(pdm_mic) != 0) {
printf("PDM microphone start failed!\n");
while (1) { tight_loop_contents(); }
}
printf("PDM microphone started\n");
uint32_t printed = 0;
while (true) {
if (data_valid) {
data_valid = false;
int n = pdm_microphone_read(pdm_mic, sample_buffer, SAMPLE_BUFFER_SIZE);
if (n > 0) {
for (int i = 0; i < n; i++) {
printf("%04x\n", (uint16_t)sample_buffer[i]);
}
}
}
sleep_ms(1);
}
}
\ No newline at end of file
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
endif ()
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
endif()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
)
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
GIT_SUBMODULES_RECURSE FALSE
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
else ()
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
endif ()
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Repository toolbox
Import into Mbed Studio
Export to desktop IDE
Repository details
Type: Library
Created: 27 Apr 2017
Imports: 122
Forks: 1
Commits: 27
Dependents: 4
Dependencies: 3
Followers: 387
The code in this repository is Apache licensed.
Components
X-NUCLEO-CCA02M1 Digital MEMS Microphones Expansion Board.
/**
*******************************************************************************
* @file OpenPDMFilter.c
* @author CL
* @version V1.0.0
* @date 9-September-2015
* @brief Open PDM audio software decoding Library.
* This Library is used to decode and reconstruct the audio signal
* produced by ST MEMS microphone (MP45Dxxx, MP34Dxxx).
*******************************************************************************
* @attention
*
* <h2><center>&copy; COPYRIGHT 2018 STMicroelectronics</center></h2>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "OpenPDMFilter.h"
/* Variables -----------------------------------------------------------------*/
uint32_t div_const = 0;
int64_t sub_const = 0;
uint32_t sinc[DECIMATION_MAX * SINCN];
uint32_t sinc1[DECIMATION_MAX];
uint32_t sinc2[DECIMATION_MAX * 2];
uint32_t coef[SINCN][DECIMATION_MAX];
#ifdef USE_LUT
int32_t lut[256][DECIMATION_MAX / 8][SINCN];
#endif
/* Functions -----------------------------------------------------------------*/
#ifdef USE_LUT
int32_t filter_table_mono_64(uint8_t *data, uint8_t sincn)
{
return (int32_t)
lut[data[0]][0][sincn] +
lut[data[1]][1][sincn] +
lut[data[2]][2][sincn] +
lut[data[3]][3][sincn] +
lut[data[4]][4][sincn] +
lut[data[5]][5][sincn] +
lut[data[6]][6][sincn] +
lut[data[7]][7][sincn];
}
int32_t filter_table_stereo_64(uint8_t *data, uint8_t sincn)
{
return (int32_t)
lut[data[0]][0][sincn] +
lut[data[2]][1][sincn] +
lut[data[4]][2][sincn] +
lut[data[6]][3][sincn] +
lut[data[8]][4][sincn] +
lut[data[10]][5][sincn] +
lut[data[12]][6][sincn] +
lut[data[14]][7][sincn];
}
int32_t filter_table_mono_128(uint8_t *data, uint8_t sincn)
{
return (int32_t)
lut[data[0]][0][sincn] +
lut[data[1]][1][sincn] +
lut[data[2]][2][sincn] +
lut[data[3]][3][sincn] +
lut[data[4]][4][sincn] +
lut[data[5]][5][sincn] +
lut[data[6]][6][sincn] +
lut[data[7]][7][sincn] +
lut[data[8]][8][sincn] +
lut[data[9]][9][sincn] +
lut[data[10]][10][sincn] +
lut[data[11]][11][sincn] +
lut[data[12]][12][sincn] +
lut[data[13]][13][sincn] +
lut[data[14]][14][sincn] +
lut[data[15]][15][sincn];
}
int32_t filter_table_stereo_128(uint8_t *data, uint8_t sincn)
{
return (int32_t)
lut[data[0]][0][sincn] +
lut[data[2]][1][sincn] +
lut[data[4]][2][sincn] +
lut[data[6]][3][sincn] +
lut[data[8]][4][sincn] +
lut[data[10]][5][sincn] +
lut[data[12]][6][sincn] +
lut[data[14]][7][sincn] +
lut[data[16]][8][sincn] +
lut[data[18]][9][sincn] +
lut[data[20]][10][sincn] +
lut[data[22]][11][sincn] +
lut[data[24]][12][sincn] +
lut[data[26]][13][sincn] +
lut[data[28]][14][sincn] +
lut[data[30]][15][sincn];
}
int32_t (* filter_tables_64[2]) (uint8_t *data, uint8_t sincn) = {filter_table_mono_64, filter_table_stereo_64};
int32_t (* filter_tables_128[2]) (uint8_t *data, uint8_t sincn) = {filter_table_mono_128, filter_table_stereo_128};
#else
int32_t filter_table(uint8_t *data, uint8_t sincn, TPDMFilter_InitStruct *param)
{
uint8_t c, i;
uint16_t data_index = 0;
uint32_t *coef_p = &coef[sincn][0];
int32_t F = 0;
uint8_t decimation = param->Decimation;
uint8_t channels = param->In_MicChannels;
for (i = 0; i < decimation; i += 8) {
c = data[data_index];
F += ((c >> 7) ) * coef_p[i ] +
((c >> 6) & 0x01) * coef_p[i + 1] +
((c >> 5) & 0x01) * coef_p[i + 2] +
((c >> 4) & 0x01) * coef_p[i + 3] +
((c >> 3) & 0x01) * coef_p[i + 4] +
((c >> 2) & 0x01) * coef_p[i + 5] +
((c >> 1) & 0x01) * coef_p[i + 6] +
((c ) & 0x01) * coef_p[i + 7];
data_index += channels;
}
return F;
}
#endif
void convolve(uint32_t Signal[/* SignalLen */], unsigned short SignalLen,
uint32_t Kernel[/* KernelLen */], unsigned short KernelLen,
uint32_t Result[/* SignalLen + KernelLen - 1 */])
{
uint16_t n;
for (n = 0; n < SignalLen + KernelLen - 1; n++)
{
unsigned short kmin, kmax, k;
Result[n] = 0;
kmin = (n >= KernelLen - 1) ? n - (KernelLen - 1) : 0;
kmax = (n < SignalLen - 1) ? n : SignalLen - 1;
for (k = kmin; k <= kmax; k++) {
Result[n] += Signal[k] * Kernel[n - k];
}
}
}
void Open_PDM_Filter_Init(TPDMFilter_InitStruct *Param)
{
uint16_t i, j;
int64_t sum = 0;
uint8_t decimation = Param->Decimation;
for (i = 0; i < SINCN; i++) {
Param->Coef[i] = 0;
Param->bit[i] = 0;
}
for (i = 0; i < decimation; i++) {
sinc1[i] = 1;
}
Param->OldOut = Param->OldIn = Param->OldZ = 0;
Param->LP_ALFA = (Param->LP_HZ != 0 ? (uint16_t) (Param->LP_HZ * 256 / (Param->LP_HZ + Param->Fs / (2 * 3.14159))) : 0);
Param->HP_ALFA = (Param->HP_HZ != 0 ? (uint16_t) (Param->Fs * 256 / (2 * 3.14159 * Param->HP_HZ + Param->Fs)) : 0);
Param->FilterLen = decimation * SINCN;
sinc[0] = 0;
sinc[decimation * SINCN - 1] = 0;
convolve(sinc1, decimation, sinc1, decimation, sinc2);
convolve(sinc2, decimation * 2 - 1, sinc1, decimation, &sinc[1]);
for(j = 0; j < SINCN; j++) {
for (i = 0; i < decimation; i++) {
coef[j][i] = sinc[j * decimation + i];
sum += sinc[j * decimation + i];
}
}
sub_const = sum >> 1;
div_const = sub_const * Param->MaxVolume / 32768 / FILTER_GAIN;
div_const = (div_const == 0 ? 1 : div_const);
#ifdef USE_LUT
/* Look-Up Table. */
uint16_t c, d, s;
for (s = 0; s < SINCN; s++)
{
uint32_t *coef_p = &coef[s][0];
for (c = 0; c < 256; c++)
for (d = 0; d < decimation / 8; d++)
lut[c][d][s] = ((c >> 7) ) * coef_p[d * 8 ] +
((c >> 6) & 0x01) * coef_p[d * 8 + 1] +
((c >> 5) & 0x01) * coef_p[d * 8 + 2] +
((c >> 4) & 0x01) * coef_p[d * 8 + 3] +
((c >> 3) & 0x01) * coef_p[d * 8 + 4] +
((c >> 2) & 0x01) * coef_p[d * 8 + 5] +
((c >> 1) & 0x01) * coef_p[d * 8 + 6] +
((c ) & 0x01) * coef_p[d * 8 + 7];
}
#endif
}
void Open_PDM_Filter_64(uint8_t* data, uint16_t* dataOut, uint16_t volume, TPDMFilter_InitStruct *Param)
{
uint8_t i, data_out_index;
uint8_t channels = Param->In_MicChannels;
uint8_t data_inc = ((DECIMATION_MAX >> 4) * channels);
int64_t Z, Z0, Z1, Z2;
int64_t OldOut, OldIn, OldZ;
OldOut = Param->OldOut;
OldIn = Param->OldIn;
OldZ = Param->OldZ;
#ifdef USE_LUT
uint8_t j = channels - 1;
#endif
for (i = 0, data_out_index = 0; i < Param->Fs / 1000; i++, data_out_index += channels) {
#ifdef USE_LUT
Z0 = filter_tables_64[j](data, 0);
Z1 = filter_tables_64[j](data, 1);
Z2 = filter_tables_64[j](data, 2);
#else
Z0 = filter_table(data, 0, Param);
Z1 = filter_table(data, 1, Param);
Z2 = filter_table(data, 2, Param);
#endif
Z = Param->Coef[1] + Z2 - sub_const;
Param->Coef[1] = Param->Coef[0] + Z1;
Param->Coef[0] = Z0;
OldOut = (Param->HP_ALFA * (OldOut + Z - OldIn)) >> 8;
OldIn = Z;
OldZ = ((256 - Param->LP_ALFA) * OldZ + Param->LP_ALFA * OldOut) >> 8;
Z = OldZ * volume;
Z = RoundDiv(Z, div_const);
Z = SaturaLH(Z, -32700, 32700);
dataOut[data_out_index] = Z;
data += data_inc;
}
Param->OldOut = OldOut;
Param->OldIn = OldIn;
Param->OldZ = OldZ;
}
void Open_PDM_Filter_128(uint8_t* data, uint16_t* dataOut, uint16_t volume, TPDMFilter_InitStruct *Param)
{
uint8_t i, data_out_index;
uint8_t channels = Param->In_MicChannels;
uint8_t data_inc = ((DECIMATION_MAX >> 3) * channels);
int64_t Z, Z0, Z1, Z2;
int64_t OldOut, OldIn, OldZ;
OldOut = Param->OldOut;
OldIn = Param->OldIn;
OldZ = Param->OldZ;
#ifdef USE_LUT
uint8_t j = channels - 1;
#endif
for (i = 0, data_out_index = 0; i < Param->Fs / 1000; i++, data_out_index += channels) {
#ifdef USE_LUT
Z0 = filter_tables_128[j](data, 0);
Z1 = filter_tables_128[j](data, 1);
Z2 = filter_tables_128[j](data, 2);
#else
Z0 = filter_table(data, 0, Param);
Z1 = filter_table(data, 1, Param);
Z2 = filter_table(data, 2, Param);
#endif
Z = Param->Coef[1] + Z2 - sub_const;
Param->Coef[1] = Param->Coef[0] + Z1;
Param->Coef[0] = Z0;
OldOut = (Param->HP_ALFA * (OldOut + Z - OldIn)) >> 8;
OldIn = Z;
OldZ = ((256 - Param->LP_ALFA) * OldZ + Param->LP_ALFA * OldOut) >> 8;
Z = OldZ * volume;
Z = RoundDiv(Z, div_const);
Z = SaturaLH(Z, -32700, 32700);
dataOut[data_out_index] = Z;
data += data_inc;
}
Param->OldOut = OldOut;
Param->OldIn = OldIn;
Param->OldZ = OldZ;
}
/**
*******************************************************************************
* @file OpenPDMFilter.h
* @author CL
* @version V1.0.0
* @date 9-September-2015
* @brief Header file for Open PDM audio software decoding Library.
* This Library is used to decode and reconstruct the audio signal
* produced by ST MEMS microphone (MP45Dxxx, MP34Dxxx).
*******************************************************************************
* @attention
*
* <h2><center>&copy; COPYRIGHT 2018 STMicroelectronics</center></h2>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************
*/
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __OPENPDMFILTER_H
#define __OPENPDMFILTER_H
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
/* Definitions ---------------------------------------------------------------*/
/*
* Enable to use a Look-Up Table to improve performances while using more FLASH
* and RAM memory.
* Note: Without Look-Up Table up to stereo@16KHz configuration is supported.
*/
#define USE_LUT
#define SINCN 3
#define DECIMATION_MAX 128
#ifdef PICO_BUILD
#define FILTER_GAIN Param->Gain
#else
#define FILTER_GAIN 16
#endif
#define HTONS(A) ((((uint16_t)(A) & 0xff00) >> 8) | \
(((uint16_t)(A) & 0x00ff) << 8))
#define RoundDiv(a, b) (((a)>0)?(((a)+(b)/2)/(b)):(((a)-(b)/2)/(b)))
#define SaturaLH(N, L, H) (((N)<(L))?(L):(((N)>(H))?(H):(N)))
/* Types ---------------------------------------------------------------------*/
typedef struct {
/* Public */
float LP_HZ;
float HP_HZ;
uint16_t Fs;
uint8_t In_MicChannels;
uint8_t Out_MicChannels;
uint8_t Decimation;
uint8_t MaxVolume;
#ifdef PICO_BUILD
uint8_t Gain;
#endif
/* Private */
uint32_t Coef[SINCN];
uint16_t FilterLen;
int64_t OldOut, OldIn, OldZ;
uint16_t LP_ALFA;
uint16_t HP_ALFA;
uint16_t bit[5];
uint16_t byte;
} TPDMFilter_InitStruct;
/* Exported functions ------------------------------------------------------- */
void Open_PDM_Filter_Init(TPDMFilter_InitStruct *init_struct);
void Open_PDM_Filter_64(uint8_t* data, uint16_t* data_out, uint16_t mic_gain, TPDMFilter_InitStruct *init_struct);
void Open_PDM_Filter_128(uint8_t* data, uint16_t* data_out, uint16_t mic_gain, TPDMFilter_InitStruct *init_struct);
#ifdef __cplusplus
}
#endif
#endif // __OPENPDMFILTER_H
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
/*
* Copyright (c) 2021 Arm Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#include <stdlib.h>
#include <string.h>
#include "hardware/adc.h"
#include "hardware/clocks.h"
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "pico/analog_microphone.h"
#define ANALOG_RAW_BUFFER_COUNT 2
static struct {
struct analog_microphone_config config;
int dma_channel;
uint16_t* raw_buffer[ANALOG_RAW_BUFFER_COUNT];
volatile int raw_buffer_write_index;
volatile int raw_buffer_read_index;
uint buffer_size;
int16_t bias;
uint dma_irq;
analog_samples_ready_handler_t samples_ready_handler;
} analog_mic;
static void analog_dma_handler();
int analog_microphone_init(const struct analog_microphone_config* config) {
memset(&analog_mic, 0x00, sizeof(analog_mic));
memcpy(&analog_mic.config, config, sizeof(analog_mic.config));
if (config->gpio < 26 || config->gpio > 29) {
return -1;
}
size_t raw_buffer_size = config->sample_buffer_size * sizeof(analog_mic.raw_buffer[0][0]);
analog_mic.buffer_size = config->sample_buffer_size;
analog_mic.bias = ((int16_t)((config->bias_voltage * 4095) / 3.3));
for (int i = 0; i < ANALOG_RAW_BUFFER_COUNT; i++) {
analog_mic.raw_buffer[i] = malloc(raw_buffer_size);
if (analog_mic.raw_buffer[i] == NULL) {
analog_microphone_deinit();
return -1;
}
}
analog_mic.dma_channel = dma_claim_unused_channel(true);
if (analog_mic.dma_channel < 0) {
analog_microphone_deinit();
return -1;
}
float clk_div = (clock_get_hz(clk_adc) / (1.0 * config->sample_rate)) - 1;
dma_channel_config dma_channel_cfg = dma_channel_get_default_config(analog_mic.dma_channel);
channel_config_set_transfer_data_size(&dma_channel_cfg, DMA_SIZE_16);
channel_config_set_read_increment(&dma_channel_cfg, false);
channel_config_set_write_increment(&dma_channel_cfg, true);
channel_config_set_dreq(&dma_channel_cfg, DREQ_ADC);
analog_mic.dma_irq = DMA_IRQ_0;
dma_channel_configure(
analog_mic.dma_channel,
&dma_channel_cfg,
analog_mic.raw_buffer[0],
&adc_hw->fifo,
analog_mic.buffer_size,
false
);
adc_gpio_init(config->gpio);
adc_init();
adc_select_input(config->gpio - 26);
adc_fifo_setup(
true, // Write each completed conversion to the sample FIFO
true, // Enable DMA data request (DREQ)
1, // DREQ (and IRQ) asserted when at least 1 sample present
false, // We won't see the ERR bit because of 8 bit reads; disable.
false // Don't shift each sample to 8 bits when pushing to FIFO
);
adc_set_clkdiv(clk_div);
}
void analog_microphone_deinit() {
for (int i = 0; i < ANALOG_RAW_BUFFER_COUNT; i++) {
if (analog_mic.raw_buffer[i]) {
free(analog_mic.raw_buffer[i]);
analog_mic.raw_buffer[i] = NULL;
}
}
if (analog_mic.dma_channel > -1) {
dma_channel_unclaim(analog_mic.dma_channel);
analog_mic.dma_channel = -1;
}
}
int analog_microphone_start() {
irq_set_enabled(analog_mic.dma_irq, true);
irq_set_exclusive_handler(analog_mic.dma_irq, analog_dma_handler);
if (analog_mic.dma_irq == DMA_IRQ_0) {
dma_channel_set_irq0_enabled(analog_mic.dma_channel, true);
} else if (analog_mic.dma_irq == DMA_IRQ_1) {
dma_channel_set_irq1_enabled(analog_mic.dma_channel, true);
} else {
return -1;
}
analog_mic.raw_buffer_write_index = 0;
analog_mic.raw_buffer_read_index = 0;
dma_channel_transfer_to_buffer_now(
analog_mic.dma_channel,
analog_mic.raw_buffer[0],
analog_mic.buffer_size
);
adc_run(true); // start running the adc
}
void analog_microphone_stop() {
adc_run(false); // stop running the adc
dma_channel_abort(analog_mic.dma_channel);
if (analog_mic.dma_irq == DMA_IRQ_0) {
dma_channel_set_irq0_enabled(analog_mic.dma_channel, false);
} else if (analog_mic.dma_irq == DMA_IRQ_1) {
dma_channel_set_irq1_enabled(analog_mic.dma_channel, false);
}
irq_set_enabled(analog_mic.dma_irq, false);
}
static void analog_dma_handler() {
// clear IRQ
if (analog_mic.dma_irq == DMA_IRQ_0) {
dma_hw->ints0 = (1u << analog_mic.dma_channel);
} else if (analog_mic.dma_irq == DMA_IRQ_1) {
dma_hw->ints1 = (1u << analog_mic.dma_channel);
}
// get the current buffer index
analog_mic.raw_buffer_read_index = analog_mic.raw_buffer_write_index;
// get the next capture index to send the dma to start
analog_mic.raw_buffer_write_index = (analog_mic.raw_buffer_write_index + 1) % ANALOG_RAW_BUFFER_COUNT;
// give the channel a new buffer to write to and re-trigger it
dma_channel_transfer_to_buffer_now(
analog_mic.dma_channel,
analog_mic.raw_buffer[analog_mic.raw_buffer_write_index],
analog_mic.buffer_size
);
if (analog_mic.samples_ready_handler) {
analog_mic.samples_ready_handler();
}
}
void analog_microphone_set_samples_ready_handler(analog_samples_ready_handler_t handler) {
analog_mic.samples_ready_handler = handler;
}
int analog_microphone_read(int16_t* buffer, size_t samples) {
if (samples > analog_mic.config.sample_buffer_size) {
samples = analog_mic.config.sample_buffer_size;
}
if (analog_mic.raw_buffer_write_index == analog_mic.raw_buffer_read_index) {
return 0;
}
uint16_t* in = analog_mic.raw_buffer[analog_mic.raw_buffer_read_index];
int16_t* out = buffer;
int16_t bias = analog_mic.bias;
analog_mic.raw_buffer_read_index++;
for (int i = 0; i < samples; i++) {
*out++ = *in++ - bias;
}
return samples;
}
; Receive a mono or stereo I2S audio stream as stereo
; This is 64 bits per sample; can be altered by modifying the "set" params,
; or made programmable by replacing "set x" with "mov x, y" and using Y as a config register.
;
; Autopull must be enabled, with threshold set to 64.
; Since I2S is MSB-first, shift direction should be to left.
; Hence the format of the FIFO word is:
;
; | 31 : 0 | 31 : 0 |
; | sample ws=0 | sample ws=1 |
;
; Data is output at 1 bit per clock. Use clock divider to adjust frequency.
; Fractional divider will probably be needed to get correct bit clock period,
; but for common syslck freqs this should still give a constant word select period.
;
; One output pin is used for the data output.
; Two side-set pins are used. Bit 0 is clock, bit 1 is word select.
; Send 32 bit words to the PIO for mono, 64 bit words for stereo
.program audio_i2s_rx_32b
.side_set 2
; /--- LRCLK
; |/-- BCLK
; ||
.wrap_target
set x, 30 side 0b00
left_channel:
in pins, 1 side 0b01
jmp x-- left_channel side 0b00
in pins, 1 side 0b11
set x, 30 side 0b10
right_channel:
in pins, 1 side 0b11
jmp x-- right_channel side 0b10
in pins, 1 side 0b01
.wrap
% c-sdk {
%}
\ No newline at end of file
; Transmit a mono or stereo I2S audio stream as stereo
; This is 16 bits per sample; can be altered by modifying the "set" params,
; or made programmable by replacing "set x" with "mov x, y" and using Y as a config register.
;
; Autopull must be enabled, with threshold set to 32.
; Since I2S is MSB-first, shift direction should be to left.
; Hence the format of the FIFO word is:
;
; | 31 : 16 | 15 : 0 |
; | sample ws=0 | sample ws=1 |
;
; Data is output at 1 bit per clock. Use clock divider to adjust frequency.
; Fractional divider will probably be needed to get correct bit clock period,
; but for common syslck freqs this should still give a constant word select period.
;
; One output pin is used for the data output.
; Two side-set pins are used. Bit 0 is clock, bit 1 is word select.
; Send 16 bit words to the PIO for mono, 32 bit words for stereo
.program audio_i2s_tx_16b
.side_set 2
; /--- LRCLK
; |/-- BCLK
; ||
.wrap_target
set x, 14 side 0b01
left_channel:
out pins, 1 side 0b00
jmp x-- left_channel side 0b01
out pins, 1 side 0b10
set x, 14 side 0b11
right_channel:
out pins, 1 side 0b10
jmp x-- right_channel side 0b11
public entry_point:
out pins, 1 side 0b00
.wrap
% c-sdk {
static inline void audio_i2s_tx_16b_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base) {
pio_sm_config sm_config = audio_i2s_tx_16b_program_get_default_config(offset);
sm_config_set_out_pins(&sm_config, data_pin, 1);
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
sm_config_set_out_shift(&sm_config, false, true, 32);
pio_sm_init(pio, sm, offset, &sm_config);
uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
pio_sm_set_pins(pio, sm, 0); // clear pins
pio_sm_exec(pio, sm, pio_encode_jmp(offset + audio_i2s_tx_16b_offset_entry_point));
}
%}
\ No newline at end of file
; Transmit a mono or stereo I2S audio stream as stereo
; This is 64 bits per sample; can be altered by modifying the "set" params,
; or made programmable by replacing "set x" with "mov x, y" and using Y as a config register.
;
; Autopull must be enabled, with threshold set to 64.
; Since I2S is MSB-first, shift direction should be to left.
; Hence the format of the FIFO word is:
;
; | 31 : 0 | 31 : 0 |
; | sample ws=0 | sample ws=1 |
;
; Data is output at 1 bit per clock. Use clock divider to adjust frequency.
; Fractional divider will probably be needed to get correct bit clock period,
; but for common syslck freqs this should still give a constant word select period.
;
; One output pin is used for the data output.
; Two side-set pins are used. Bit 0 is clock, bit 1 is word select.
; Send 32 bit words to the PIO for mono, 64 bit words for stereo
.program audio_i2s_tx_32b
.side_set 2
; /--- LRCLK
; |/-- BCLK
; ||
.wrap_target
set x, 30 side 0b01
left_channel:
out pins, 1 side 0b00
jmp x-- left_channel side 0b01
out pins, 1 side 0b10
set x, 30 side 0b11
right_channel:
out pins, 1 side 0b10
jmp x-- right_channel side 0b11
public entry_point:
out pins, 1 side 0b00
.wrap
% c-sdk {
static inline void audio_i2s_tx_32b_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base) {
pio_sm_config sm_config = audio_i2s_tx_32b_program_get_default_config(offset);
sm_config_set_out_pins(&sm_config, data_pin, 1);
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
sm_config_set_out_shift(&sm_config, false, true, 32);
pio_sm_init(pio, sm, offset, &sm_config);
uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
pio_sm_set_pins(pio, sm, 0); // clear pins
pio_sm_exec(pio, sm, pio_encode_jmp(offset + audio_i2s_tx_32b_offset_entry_point));
}
%}
\ No newline at end of file
#include "pico/dc_offset_filter.h"
void dc_offset_filter_init(dc_offset_filter_t* self, int32_t apply_delay_samples){
self->sample_mean_value_sum = 0;
self->sample_mean_value_cntr = 0;
self->sample_mean_value = 0;
self->apply_delay_samples = apply_delay_samples;
}
int32_t dc_offset_filter_main(dc_offset_filter_t* self, int32_t input_sample, bool update_mean_val){
int32_t sample_mean_value_approx;
// Update mean value
if((update_mean_val == true) && (self->sample_mean_value_cntr >= self->apply_delay_samples)){
self->sample_mean_value = self->sample_mean_value_sum / self->sample_mean_value_cntr;
}
// Use mean value if we have enough input samples
if(self->sample_mean_value_cntr <= self->apply_delay_samples)
sample_mean_value_approx = 0;
else
sample_mean_value_approx = self->sample_mean_value;
// increase counters
self->sample_mean_value_sum = self->sample_mean_value_sum + input_sample;
self->sample_mean_value_cntr ++;
// return output value subtracting mean value:
return input_sample - sample_mean_value_approx;
}
\ No newline at end of file
/*
* Copyright (c) 2021 Arm Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#ifndef _PICO_ANALOG_MICROPHONE_H_
#define _PICO_ANALOG_MICROPHONE_H_
typedef void (*analog_samples_ready_handler_t)(void);
struct analog_microphone_config {
uint gpio;
float bias_voltage;
uint sample_rate;
uint sample_buffer_size;
};
int analog_microphone_init(const struct analog_microphone_config* config);
void analog_microphone_deinit();
int analog_microphone_start();
void analog_microphone_stop();
void analog_microphone_set_samples_ready_handler(analog_samples_ready_handler_t handler);
int analog_microphone_read(int16_t* buffer, size_t samples);
#endif
#ifndef DC_OFFSET_FILTER__H
#define DC_OFFSET_FILTER__H
#include <stdio.h>
#include <stdbool.h>
typedef struct _dc_offset_filter_t {
int64_t sample_mean_value_sum;
int32_t sample_mean_value_cntr;
int32_t sample_mean_value;
int32_t apply_delay_samples;
} dc_offset_filter_t;
void dc_offset_filter_init(dc_offset_filter_t* self, int32_t apply_delay_samples);
int32_t dc_offset_filter_main(dc_offset_filter_t* self, int32_t input_sample, bool update_mean_val);
#endif //DC_OFFSET_FILTER__H
#ifndef DEFAULT_I2S_BOARD_DEFINES__H
#define DEFAULT_I2S_BOARD_DEFINES__H
//-------------------------
// I2s defines
//-------------------------
#define I2S_MIC_INMP441
#ifdef I2S_MIC_INMP441
#ifndef I2S_MIC_SD
#define I2S_MIC_SD 14
#endif //I2S_MIC_SD
#ifndef I2S_MIC_SCK
#define I2S_MIC_SCK 15
#endif //I2S_MIC_SCK
#ifndef I2S_MIC_WS
#define I2S_MIC_WS (I2S_MIC_SCK+1) // needs to be I2S_MIC_SCK +1
#endif //I2S_MIC_WS
#else //I2S_MIC_INMP441
#ifndef I2S_MIC_SPH_DC_OFFSET
#define I2S_MIC_SPH_DC_OFFSET 0xf8c80000
#endif //I2S_MIC_SPH_DC_OFFSET
#ifndef I2S_MIC_SD
#define I2S_MIC_SD 10
#endif //I2S_MIC_SD
#ifndef I2S_MIC_SCK
#define I2S_MIC_SCK 11
#endif //I2S_MIC_SCK
#ifndef I2S_MIC_WS
#define I2S_MIC_WS (I2S_MIC_SCK+1) // needs to be I2S_MIC_SCK +1
#endif //I2S_MIC_WS
#endif //I2S_MIC_INMP441
#ifndef I2S_MIC_BPS
#define I2S_MIC_BPS 32 // 24 is not valid in this implementation, but INMP441 outputs 24 bits samples
#endif //I2S_MIC_BPS
#ifndef I2S_MIC_RATE_DEF
#define I2S_MIC_RATE_DEF (16000)
#endif //I2S_MIC_RATE_DEF
#ifndef I2S_SPK_SD
#define I2S_SPK_SD 2
#endif //I2S_SPK_SD
#ifndef I2S_SPK_SCK
#define I2S_SPK_SCK 3
#endif //I2S_SPK_SCK
#ifndef I2S_SPK_WS
#define I2S_SPK_WS (I2S_SPK_SCK+1) // needs to be SPK_SCK +1
#endif //I2S_SPK_WS
#ifndef I2S_SPK_BPS
#define I2S_SPK_BPS 32
#endif //I2S_SPK_BPS
#ifndef I2S_SPK_RATE_DEF
#define I2S_SPK_RATE_DEF (48000)
#endif //I2S_SPK_RATE_DEF
typedef struct {
uint32_t left;
uint32_t right;
} i2s_32b_audio_sample;
typedef struct {
uint16_t left;
uint16_t right;
} i2s_16b_audio_sample;
//-------------------------
#endif //DEFAULT_I2S_BOARD_DEFINES__H
\ No newline at end of file
#ifndef MACHINE_I2S__H
#define MACHINE_I2S__H
#include <stdlib.h>
#include <string.h>
#include "hardware/pio.h"
#include "hardware/clocks.h"
#include "hardware/gpio.h"
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "pico/ring_buf.h"
// Notes on this port's specific implementation of I2S:
// - the DMA IRQ handler is used to implement the asynchronous background operations, for non-blocking mode
// - the PIO is used to drive the I2S bus signals
// - all sample data transfers use non-blocking DMA
// - the DMA controller is configured with 2 DMA channels in chained mode
#define MAX_I2S_RP2 (2)
// The DMA buffer size was empirically determined. It is a tradeoff between:
// 1. memory use (smaller buffer size desirable to reduce memory footprint)
// 2. interrupt frequency (larger buffer size desirable to reduce interrupt frequency)
#define SIZEOF_DMA_BUFFER_IN_BYTES (96*8*2) // Max frequency is 96000. in worst case. 1ms contains 96 samples. Each sample is 8 bytes. Need to hold 2 buffers of this size
#define SIZEOF_HALF_DMA_BUFFER_IN_BYTES (SIZEOF_DMA_BUFFER_IN_BYTES / 2)
#define I2S_NUM_DMA_CHANNELS (2)
// For non-blocking mode, to avoid underflow/overflow, sample data is written/read to/from the ring buffer at a rate faster
// than the DMA transfer rate
#define NON_BLOCKING_RATE_MULTIPLIER (4)
#define SIZEOF_NON_BLOCKING_COPY_IN_BYTES (SIZEOF_HALF_DMA_BUFFER_IN_BYTES * NON_BLOCKING_RATE_MULTIPLIER)
#define NUM_I2S_USER_FORMATS (4)
#define I2S_RX_FRAME_SIZE_IN_BYTES (8)
#define SAMPLES_PER_FRAME (2)
#define PIO_INSTRUCTIONS_PER_BIT (2)
#define STATIC static
#define mp_hal_pin_obj_t uint
#ifndef m_new
#define m_new(type, num) ((type *)(malloc(sizeof(type) * (num))))
#endif //m_new
#ifndef m_new_obj
#define m_new_obj(type) (m_new(type, 1))
#endif //m_new_obj
typedef enum {
RX,
TX
} i2s_mode_t;
typedef enum {
MONO,
STEREO
} format_t;
typedef enum {
BLOCKING,
NON_BLOCKING,
UASYNCIO
} io_mode_t;
typedef enum {
GP_INPUT = 0,
GP_OUTPUT = 1
} gpio_dir_t;
// Buffer protocol
typedef struct _mp_buffer_info_t {
void *buf; // can be NULL if len == 0
size_t len; // in bytes
int typecode; // as per binary.h
} mp_buffer_info_t;
typedef struct _non_blocking_descriptor_t {
mp_buffer_info_t appbuf;
uint32_t index;
bool copy_in_progress;
} non_blocking_descriptor_t;
typedef struct _machine_i2s_obj_t {
uint8_t i2s_id;
mp_hal_pin_obj_t sck;
mp_hal_pin_obj_t ws;
mp_hal_pin_obj_t sd;
i2s_mode_t mode;
int8_t bits;
format_t format;
int32_t rate;
int32_t ibuf;
io_mode_t io_mode;
PIO pio;
uint8_t sm;
const pio_program_t *pio_program;
uint prog_offset;
int dma_channel[I2S_NUM_DMA_CHANNELS];
uint8_t dma_buffer[SIZEOF_DMA_BUFFER_IN_BYTES];
ring_buf_t ring_buffer;
uint8_t *ring_buffer_storage;
non_blocking_descriptor_t non_blocking_descriptor;
uint32_t sizeof_half_dma_buffer_in_bytes;
uint32_t sizeof_non_blocking_copy_in_bytes;
} machine_i2s_obj_t;
machine_i2s_obj_t* create_machine_i2s(uint8_t i2s_id,
mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd,
i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format,
int32_t ring_buffer_len, int32_t i2s_rate);
int machine_i2s_read_stream(machine_i2s_obj_t *self, void *buf_in, size_t size);
int machine_i2s_write_stream(machine_i2s_obj_t *self, void *buf_in, size_t size);
//void update_pio_frequency(machine_i2s_obj_t *self, uint32_t sample_freq);
#endif //MACHINE_I2S__H
\ No newline at end of file
/*
* Copyright (c) 2021 Arm Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#ifndef _PICO_PDM_MICROPHONE_H_
#define _PICO_PDM_MICROPHONE_H_
#include "hardware/pio.h"
#include "OpenPDMFilter.h"
#define MAX_PDM_RP2 (2)
#ifndef STATIC
#define STATIC static
#endif //STATIC
#ifndef m_new
#define m_new(type, num) ((type *)(malloc(sizeof(type) * (num))))
#endif //m_new
#ifndef m_new_obj
#define m_new_obj(type) (m_new(type, 1))
#endif //m_new_obj
#define PDM_DECIMATION 64
#define PDM_RAW_BUFFER_COUNT 2
typedef void (*pdm_samples_ready_handler_t)(uint8_t pdm_id);
typedef struct __pdm_microphone_config{
uint8_t pdm_id;
PIO pio;
uint dma_irq;
uint gpio_data;
uint gpio_clk;
uint sample_rate;
uint sample_buffer_size;
} pdm_microphone_config;
typedef struct __pdm_mic_obj{
uint8_t pdm_id;
pdm_microphone_config* config;
uint pio_sm;
uint pio_sm_offset;
int dma_channel;
uint8_t* raw_buffer[PDM_RAW_BUFFER_COUNT];
volatile int raw_buffer_write_index;
volatile int raw_buffer_read_index;
uint raw_buffer_size;
TPDMFilter_InitStruct filter;
uint16_t filter_volume;
pdm_samples_ready_handler_t samples_ready_handler;
} pdm_mic_obj;
pdm_mic_obj* pdm_microphone_init(pdm_microphone_config* config);
void pdm_microphone_deinit(pdm_mic_obj *pdm_mic);
int pdm_microphone_start(pdm_mic_obj *pdm_mic);
void pdm_microphone_stop(pdm_mic_obj *pdm_mic);
void pdm_microphone_set_samples_ready_handler(pdm_mic_obj *pdm_mic, pdm_samples_ready_handler_t handler);
void pdm_microphone_set_filter_max_volume(pdm_mic_obj *pdm_mic, uint8_t max_volume);
void pdm_microphone_set_filter_gain(pdm_mic_obj *pdm_mic, uint8_t gain);
void pdm_microphone_set_filter_volume(pdm_mic_obj *pdm_mic, uint16_t volume);
int pdm_microphone_read(pdm_mic_obj *pdm_mic, int16_t* buffer, size_t samples);
#endif
#ifndef RING_BUF__H
#define RING_BUF__H
#include <stdio.h>
#include <stdbool.h>
typedef struct _ring_buf_t {
uint8_t *buffer;
size_t head;
size_t tail;
size_t size;
} ring_buf_t;
void ringbuf_init(ring_buf_t *rbuf, uint8_t *buffer, size_t size);
bool ringbuf_push(ring_buf_t *rbuf, uint8_t data);
bool ringbuf_pop(ring_buf_t *rbuf, uint8_t *data);
bool ringbuf_is_empty(ring_buf_t *rbuf);
bool ringbuf_is_full(ring_buf_t *rbuf);
size_t ringbuf_available_data(ring_buf_t *rbuf);
size_t ringbuf_available_space(ring_buf_t *rbuf);
#endif //RING_BUF__H
\ No newline at end of file
#pragma once
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "stdbool.h"
// todo this seemed like aood guess, but is not correct
static const uint16_t db_to_vol[91] = {
0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0002, 0x0002,
0x0002, 0x0002, 0x0003, 0x0003, 0x0004, 0x0004, 0x0005, 0x0005,
0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000d, 0x000e,
0x0010, 0x0012, 0x0014, 0x0017, 0x001a, 0x001d, 0x0020, 0x0024,
0x0029, 0x002e, 0x0033, 0x003a, 0x0041, 0x0049, 0x0052, 0x005c,
0x0067, 0x0074, 0x0082, 0x0092, 0x00a4, 0x00b8, 0x00ce, 0x00e7,
0x0104, 0x0124, 0x0147, 0x016f, 0x019c, 0x01ce, 0x0207, 0x0246,
0x028d, 0x02dd, 0x0337, 0x039b, 0x040c, 0x048a, 0x0518, 0x05b7,
0x066a, 0x0732, 0x0813, 0x090f, 0x0a2a, 0x0b68, 0x0ccc, 0x0e5c,
0x101d, 0x1214, 0x1449, 0x16c3, 0x198a, 0x1ca7, 0x2026, 0x2413,
0x287a, 0x2d6a, 0x32f5, 0x392c, 0x4026, 0x47fa, 0x50c3, 0x5a9d,
0x65ac, 0x7214, 0x7fff
};
// actually windows doesn't seem to like this in the middle, so set top range to 0db
#define CENTER_VOLUME_INDEX 91
#define ENCODE_DB(x) ((uint16_t)(int16_t)((x)*256))
#define MIN_VOLUME ENCODE_DB(-CENTER_VOLUME_INDEX)
#define DEFAULT_VOLUME ENCODE_DB(0)
#define MAX_VOLUME ENCODE_DB(count_of(db_to_vol)-CENTER_VOLUME_INDEX)
#define VOLUME_RESOLUTION ENCODE_DB(1)
uint16_t vol_to_db_convert(bool channel_mute, uint16_t channel_volume);
\ No newline at end of file
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 Mike Teachman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
// This file is never compiled standalone, it's included directly from
// extmod/machine_i2s.c via MICROPY_PY_MACHINE_I2S_INCLUDEFILE.
#include "pico/machine_i2s.h"
#include "audio_i2s_tx_16b.pio.h"
#include "audio_i2s_tx_32b.pio.h"
#include "audio_i2s_rx_32b.pio.h"
STATIC machine_i2s_obj_t* machine_i2s_obj[MAX_I2S_RP2] = {NULL, NULL};
// The frame map is used with the readinto() method to transform the audio sample data coming
// from DMA memory (32-bit stereo) to the format specified
// in the I2S constructor. e.g. 16-bit mono
STATIC const int8_t i2s_frame_map[NUM_I2S_USER_FORMATS][I2S_RX_FRAME_SIZE_IN_BYTES] = {
{-1, -1, 0, 1, -1, -1, -1, -1 }, // Mono, 16-bits
{ 0, 1, 2, 3, -1, -1, -1, -1 }, // Mono, 32-bits
{-1, -1, 0, 1, -1, -1, 2, 3 }, // Stereo, 16-bits
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // Stereo, 32-bits
};
STATIC const PIO pio_instances[NUM_PIOS] = {pio0, pio1};
// PIO program for 16-bit write
// set(x, 14) .side(0b01)
// label('left_channel')
// out(pins, 1) .side(0b00)
// jmp(x_dec, "left_channel") .side(0b01)
// out(pins, 1) .side(0b10)
// set(x, 14) .side(0b11)
// label('right_channel')
// out(pins, 1) .side(0b10)
// jmp(x_dec, "right_channel") .side(0b11)
// out(pins, 1) .side(0b00)
STATIC const uint16_t pio_instructions_write_16[] = {59438, 24577, 2113, 28673, 63534, 28673, 6213, 24577};
STATIC const pio_program_t pio_write_16 = {
pio_instructions_write_16,
sizeof(pio_instructions_write_16) / sizeof(uint16_t),
-1
};
// PIO program for 32-bit write
// set(x, 30) .side(0b01)
// label('left_channel')
// out(pins, 1) .side(0b00)
// jmp(x_dec, "left_channel") .side(0b01)
// out(pins, 1) .side(0b10)
// set(x, 30) .side(0b11)
// label('right_channel')
// out(pins, 1) .side(0b10)
// jmp(x_dec, "right_channel") .side(0b11)
// out(pins, 1) .side(0b00)
STATIC const uint16_t pio_instructions_write_32[] = {59454, 24577, 2113, 28673, 63550, 28673, 6213, 24577};
STATIC const pio_program_t pio_write_32 = {
pio_instructions_write_32,
sizeof(pio_instructions_write_32) / sizeof(uint16_t),
-1
};
// PIO program for 32-bit read
// set(x, 30) .side(0b00)
// label('left_channel')
// in_(pins, 1) .side(0b01)
// jmp(x_dec, "left_channel") .side(0b00)
// in_(pins, 1) .side(0b11)
// set(x, 30) .side(0b10)
// label('right_channel')
// in_(pins, 1) .side(0b11)
// jmp(x_dec, "right_channel") .side(0b10)
// in_(pins, 1) .side(0b01)
STATIC const uint16_t pio_instructions_read_32[] = {57406, 18433, 65, 22529, 61502, 22529, 4165, 18433};
STATIC const pio_program_t pio_read_32 = {
pio_instructions_read_32,
sizeof(pio_instructions_read_32) / sizeof(uint16_t),
-1
};
STATIC uint8_t dma_get_bits(i2s_mode_t mode, int8_t bits);
STATIC void dma_irq0_handler(void);
STATIC void dma_irq1_handler(void);
STATIC void machine_i2s_deinit(machine_i2s_obj_t *self);
//-----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
STATIC int8_t get_frame_mapping_index(int8_t bits, format_t format) {
if (format == MONO) {
if (bits == 16) {
return 0;
} else { // 32 bits
return 1;
}
} else { // STEREO
if (bits == 16) {
return 2;
} else { // 32 bits
return 3;
}
}
}
STATIC uint32_t fill_appbuf_from_ringbuf(machine_i2s_obj_t *self, mp_buffer_info_t *appbuf) {
// copy audio samples from the ring buffer to the app buffer
// loop, copying samples until the app buffer is filled
// For uasyncio mode, the loop will make an early exit if the ring buffer becomes empty
// Example:
// a MicroPython I2S object is configured for 16-bit mono (2 bytes per audio sample).
// For every frame coming from the ring buffer (8 bytes), 2 bytes are "cherry picked" and
// copied to the supplied app buffer.
// Thus, for every 1 byte copied to the app buffer, 4 bytes are read from the ring buffer.
// If a 8kB app buffer is supplied, 32kB of audio samples is read from the ring buffer.
uint32_t num_bytes_copied_to_appbuf = 0;
uint8_t *app_p = (uint8_t *)appbuf->buf;
uint8_t appbuf_sample_size_in_bytes = (self->bits == 16? 2 : 4) * (self->format == STEREO ? 2: 1);
uint32_t num_bytes_needed_from_ringbuf = appbuf->len * (I2S_RX_FRAME_SIZE_IN_BYTES / appbuf_sample_size_in_bytes);
uint8_t discard_byte;
while (num_bytes_needed_from_ringbuf) {
uint8_t f_index = get_frame_mapping_index(self->bits, self->format);
for (uint8_t i = 0; i < I2S_RX_FRAME_SIZE_IN_BYTES; i++) {
int8_t r_to_a_mapping = i2s_frame_map[f_index][i];
if (r_to_a_mapping != -1) {
if (self->io_mode == BLOCKING) {
// poll the ringbuf until a sample becomes available, copy into appbuf using the mapping transform
while (ringbuf_pop(&self->ring_buffer, app_p + r_to_a_mapping) == false) {
;
}
num_bytes_copied_to_appbuf++;
} else if (self->io_mode == UASYNCIO) {
if (ringbuf_pop(&self->ring_buffer, app_p + r_to_a_mapping) == false) {
// ring buffer is empty, exit
goto exit;
} else {
num_bytes_copied_to_appbuf++;
}
} else {
return 0; // should never get here (non-blocking mode does not use this function)
}
} else { // r_a_mapping == -1
// discard unused byte from ring buffer
if (self->io_mode == BLOCKING) {
// poll the ringbuf until a sample becomes available
while (ringbuf_pop(&self->ring_buffer, &discard_byte) == false) {
;
}
} else if (self->io_mode == UASYNCIO) {
if (ringbuf_pop(&self->ring_buffer, &discard_byte) == false) {
// ring buffer is empty, exit
goto exit;
}
} else {
return 0; // should never get here (non-blocking mode does not use this function)
}
}
num_bytes_needed_from_ringbuf--;
}
app_p += appbuf_sample_size_in_bytes;
}
exit:
return num_bytes_copied_to_appbuf;
}
STATIC uint32_t copy_appbuf_to_ringbuf(machine_i2s_obj_t *self, mp_buffer_info_t *appbuf) {
// copy audio samples from the app buffer to the ring buffer
// loop, reading samples until the app buffer is emptied
// for uasyncio mode, the loop will make an early exit if the ring buffer becomes full
uint32_t a_index = 0;
while (a_index < appbuf->len) {
if (self->io_mode == BLOCKING) {
// copy a byte to the ringbuf when space becomes available
while (ringbuf_push(&self->ring_buffer, ((uint8_t *)appbuf->buf)[a_index]) == false) {
;
}
a_index++;
} else if (self->io_mode == UASYNCIO) {
if (ringbuf_push(&self->ring_buffer, ((uint8_t *)appbuf->buf)[a_index]) == false) {
// ring buffer is full, exit
break;
} else {
a_index++;
}
} else {
return 0; // should never get here (non-blocking mode does not use this function)
}
}
return a_index;
}
// function is used in IRQ context
static void copy_appbuf_to_ringbuf_non_blocking(machine_i2s_obj_t *self) {
// copy audio samples from app buffer into ring buffer
uint32_t num_bytes_remaining_to_copy = self->non_blocking_descriptor.appbuf.len - self->non_blocking_descriptor.index;
uint32_t num_bytes_to_copy = MIN(self->sizeof_non_blocking_copy_in_bytes, num_bytes_remaining_to_copy);
if (ringbuf_available_space(&self->ring_buffer) >= num_bytes_to_copy) {
for (uint32_t i = 0; i < num_bytes_to_copy; i++) {
ringbuf_push(&self->ring_buffer,
((uint8_t *)self->non_blocking_descriptor.appbuf.buf)[self->non_blocking_descriptor.index + i]);
}
self->non_blocking_descriptor.index += num_bytes_to_copy;
if (self->non_blocking_descriptor.index >= self->non_blocking_descriptor.appbuf.len) {
self->non_blocking_descriptor.copy_in_progress = false;
//mp_sched_schedule(self->callback_for_non_blocking, MP_OBJ_FROM_PTR(self));
}
}
}
static void fill_appbuf_from_ringbuf_non_blocking(machine_i2s_obj_t *self) {
// attempt to copy a block of audio samples from the ring buffer to the supplied app buffer.
// audio samples will be formatted as part of the copy operation
uint32_t num_bytes_copied_to_appbuf = 0;
uint8_t *app_p = &(((uint8_t *)self->non_blocking_descriptor.appbuf.buf)[self->non_blocking_descriptor.index]);
uint8_t appbuf_sample_size_in_bytes = (self->bits == 16? 2 : 4) * (self->format == STEREO ? 2: 1);
uint32_t num_bytes_remaining_to_copy_to_appbuf = self->non_blocking_descriptor.appbuf.len - self->non_blocking_descriptor.index;
uint32_t num_bytes_remaining_to_copy_from_ring_buffer = num_bytes_remaining_to_copy_to_appbuf *
(I2S_RX_FRAME_SIZE_IN_BYTES / appbuf_sample_size_in_bytes);
uint32_t num_bytes_needed_from_ringbuf = MIN(self->sizeof_non_blocking_copy_in_bytes, num_bytes_remaining_to_copy_from_ring_buffer);
uint8_t discard_byte;
if (ringbuf_available_data(&self->ring_buffer) >= num_bytes_needed_from_ringbuf) {
while (num_bytes_needed_from_ringbuf) {
uint8_t f_index = get_frame_mapping_index(self->bits, self->format);
for (uint8_t i = 0; i < I2S_RX_FRAME_SIZE_IN_BYTES; i++) {
int8_t r_to_a_mapping = i2s_frame_map[f_index][i];
if (r_to_a_mapping != -1) {
ringbuf_pop(&self->ring_buffer, app_p + r_to_a_mapping);
num_bytes_copied_to_appbuf++;
} else { // r_a_mapping == -1
// discard unused byte from ring buffer
ringbuf_pop(&self->ring_buffer, &discard_byte);
}
num_bytes_needed_from_ringbuf--;
}
app_p += appbuf_sample_size_in_bytes;
}
self->non_blocking_descriptor.index += num_bytes_copied_to_appbuf;
if (self->non_blocking_descriptor.index >= self->non_blocking_descriptor.appbuf.len) {
self->non_blocking_descriptor.copy_in_progress = false;
//mp_sched_schedule(self->callback_for_non_blocking, MP_OBJ_FROM_PTR(self));
}
}
}
//-----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
// function is used in IRQ context
STATIC void empty_dma(machine_i2s_obj_t *self, uint8_t *dma_buffer_p) {
// when space exists, copy samples into ring buffer
if (ringbuf_available_space(&self->ring_buffer) >= self->sizeof_half_dma_buffer_in_bytes) {
for (uint32_t i = 0; i < self->sizeof_half_dma_buffer_in_bytes; i++) {
ringbuf_push(&self->ring_buffer, dma_buffer_p[i]);
}
}
}
// function is used in IRQ context
STATIC uint32_t feed_dma(machine_i2s_obj_t *self, uint8_t *dma_buffer_p) {
// when data exists, copy samples from ring buffer
uint32_t available_data_bytes = ringbuf_available_data(&self->ring_buffer);
if(available_data_bytes > self->sizeof_half_dma_buffer_in_bytes)
{
available_data_bytes = self->sizeof_half_dma_buffer_in_bytes;
}
uint32_t transfer_size_in_bytes = dma_get_bits(self->mode, self->bits) / 8;
uint32_t available_data_transfers = (available_data_bytes==0) ? 0 : (available_data_bytes/transfer_size_in_bytes);
//if (available_data >= self->sizeof_half_dma_buffer_in_bytes) {
if (available_data_bytes >= self->sizeof_half_dma_buffer_in_bytes) {
// copy a block of samples from the ring buffer to the dma buffer.
// STM32 HAL API has a stereo I2S implementation, but not mono
// mono format is implemented by duplicating each sample into both L and R channels.
if ((self->format == MONO) && (self->bits == 16)) {
for (uint32_t i = 0; i < available_data_transfers; i++) {
for (uint8_t b = 0; b < sizeof(uint16_t); b++) {
ringbuf_pop(&self->ring_buffer, &dma_buffer_p[i * 4 + b]);
dma_buffer_p[i * 4 + b + 2] = dma_buffer_p[i * 4 + b]; // duplicated mono sample
}
}
} else if ((self->format == MONO) && (self->bits == 32)) {
for (uint32_t i = 0; i < available_data_transfers; i++) {
for (uint8_t b = 0; b < sizeof(uint32_t); b++) {
ringbuf_pop(&self->ring_buffer, &dma_buffer_p[i * 8 + b]);
dma_buffer_p[i * 8 + b + 4] = dma_buffer_p[i * 8 + b]; // duplicated mono sample
}
}
} else { // STEREO, both 16-bit and 32-bit
for (uint32_t i = 0; i < available_data_transfers*transfer_size_in_bytes; i++) {
ringbuf_pop(&self->ring_buffer, &dma_buffer_p[i]);
}
}
} else {
// underflow. clear buffer to transmit "silence" on the I2S bus
available_data_bytes = self->sizeof_half_dma_buffer_in_bytes;
available_data_transfers = available_data_bytes/transfer_size_in_bytes;
memset(dma_buffer_p, 0, self->sizeof_half_dma_buffer_in_bytes);
}
return available_data_transfers;
}
STATIC void irq_configure(machine_i2s_obj_t *self) {
if (self->i2s_id == 0) {
irq_add_shared_handler(DMA_IRQ_0, dma_irq0_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
irq_set_enabled(DMA_IRQ_0, true);
} else {
irq_add_shared_handler(DMA_IRQ_1, dma_irq1_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
irq_set_enabled(DMA_IRQ_1, true);
}
}
STATIC void irq_deinit(machine_i2s_obj_t *self) {
if (self->i2s_id == 0) {
irq_remove_handler(DMA_IRQ_0, dma_irq0_handler);
} else {
irq_remove_handler(DMA_IRQ_1, dma_irq1_handler);
}
}
STATIC int pio_configure(machine_i2s_obj_t *self) {
if (self->mode == TX) {
if (self->bits == 16) {
self->pio_program = &audio_i2s_tx_16b_program; //&pio_write_16;
} else {
self->pio_program = &audio_i2s_tx_32b_program; //&pio_write_32;
}
} else { // RX
self->pio_program = &audio_i2s_rx_32b_program; //&pio_read_32;
}
// find a PIO with a free state machine and adequate program space
PIO candidate_pio;
bool is_free_sm;
bool can_add_program;
for (uint8_t p = 0; p < NUM_PIOS; p++) {
candidate_pio = pio_instances[p];
is_free_sm = false;
can_add_program = false;
for (uint8_t sm = 0; sm < NUM_PIO_STATE_MACHINES; sm++) {
if (!pio_sm_is_claimed(candidate_pio, sm)) {
is_free_sm = true;
break;
}
}
if (pio_can_add_program(candidate_pio, self->pio_program)) {
can_add_program = true;
}
if (is_free_sm && can_add_program) {
break;
}
}
if (!is_free_sm) {
//mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("no free state machines"));
return -1;
}
if (!can_add_program) {
//mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("not enough PIO program space"));
return -2;
}
self->pio = candidate_pio;
self->sm = pio_claim_unused_sm(self->pio, false);
self->prog_offset = pio_add_program(self->pio, self->pio_program);
pio_sm_init(self->pio, self->sm, self->prog_offset, NULL);
pio_sm_config config = pio_get_default_sm_config();
float pio_freq = self->rate *
SAMPLES_PER_FRAME *
dma_get_bits(self->mode, self->bits) *
PIO_INSTRUCTIONS_PER_BIT;
float clkdiv = (float)(clock_get_hz(clk_sys)) / pio_freq;
sm_config_set_clkdiv(&config, clkdiv);
if (self->mode == TX) {
sm_config_set_out_pins(&config, self->sd, 1);
sm_config_set_out_shift(&config, false, true, dma_get_bits(self->mode, self->bits));
sm_config_set_fifo_join(&config, PIO_FIFO_JOIN_TX); // double TX FIFO size
} else { // RX
sm_config_set_in_pins(&config, self->sd);
sm_config_set_in_shift(&config, false, true, dma_get_bits(self->mode, self->bits));
sm_config_set_fifo_join(&config, PIO_FIFO_JOIN_RX); // double RX FIFO size
}
sm_config_set_sideset(&config, 2, false, false);
sm_config_set_sideset_pins(&config, self->sck);
sm_config_set_wrap(&config, self->prog_offset, self->prog_offset + self->pio_program->length - 1);
pio_sm_set_config(self->pio, self->sm, &config);
return 0;
}
STATIC void pio_deinit(machine_i2s_obj_t *self) {
if (self->pio) {
pio_sm_set_enabled(self->pio, self->sm, false);
pio_sm_unclaim(self->pio, self->sm);
pio_remove_program(self->pio, self->pio_program, self->prog_offset);
}
}
STATIC void gpio_init_i2s(PIO pio, uint8_t sm, mp_hal_pin_obj_t pin_num, uint8_t pin_val, gpio_dir_t pin_dir) {
uint32_t pinmask = 1 << pin_num;
pio_sm_set_pins_with_mask(pio, sm, pin_val << pin_num, pinmask);
pio_sm_set_pindirs_with_mask(pio, sm, pin_dir << pin_num, pinmask);
pio_gpio_init(pio, pin_num);
}
STATIC void gpio_configure(machine_i2s_obj_t *self) {
gpio_init_i2s(self->pio, self->sm, self->sck, 0, GP_OUTPUT);
gpio_init_i2s(self->pio, self->sm, self->ws, 0, GP_OUTPUT);
if (self->mode == TX) {
gpio_init_i2s(self->pio, self->sm, self->sd, 0, GP_OUTPUT);
} else { // RX
gpio_init_i2s(self->pio, self->sm, self->sd, 0, GP_INPUT);
}
}
STATIC uint8_t dma_get_bits(i2s_mode_t mode, int8_t bits) {
if (mode == TX) {
return bits;
} else { // RX
// always read 32 bit words for I2S e.g. I2S MEMS microphones
return 32;
}
}
// determine which DMA channel is associated to this IRQ
STATIC uint dma_map_irq_to_channel(uint irq_index) {
for (uint ch = 0; ch < NUM_DMA_CHANNELS; ch++) {
if ((dma_irqn_get_channel_status(irq_index, ch))) {
return ch;
}
}
// This should never happen
return -1;
}
// note: first DMA channel is mapped to the top half of buffer, second is mapped to the bottom half
STATIC uint8_t *dma_get_buffer(machine_i2s_obj_t *i2s_obj, uint channel) {
for (uint8_t ch = 0; ch < I2S_NUM_DMA_CHANNELS; ch++) {
if (i2s_obj->dma_channel[ch] == channel) {
return i2s_obj->dma_buffer + (i2s_obj->sizeof_half_dma_buffer_in_bytes * ch);
}
}
// This should never happen
return NULL;
}
STATIC int dma_configure(machine_i2s_obj_t *self) {
uint8_t num_free_dma_channels = 0;
for (uint8_t ch = 0; ch < NUM_DMA_CHANNELS; ch++) {
if (!dma_channel_is_claimed(ch)) {
num_free_dma_channels++;
}
}
if (num_free_dma_channels < I2S_NUM_DMA_CHANNELS) {
//mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("cannot claim 2 DMA channels"));
return -1;
}
for (uint8_t ch = 0; ch < I2S_NUM_DMA_CHANNELS; ch++) {
self->dma_channel[ch] = dma_claim_unused_channel(false);
}
// The DMA channels are chained together. The first DMA channel is used to access
// the top half of the DMA buffer. The second DMA channel accesses the bottom half of the DMA buffer.
// With chaining, when one DMA channel has completed a data transfer, the other
// DMA channel automatically starts a new data transfer.
enum dma_channel_transfer_size dma_size = (dma_get_bits(self->mode, self->bits) == 16) ? DMA_SIZE_16 : DMA_SIZE_32;
for (uint8_t ch = 0; ch < I2S_NUM_DMA_CHANNELS; ch++) {
dma_channel_config dma_config = dma_channel_get_default_config(self->dma_channel[ch]);
channel_config_set_transfer_data_size(&dma_config, dma_size);
channel_config_set_chain_to(&dma_config, self->dma_channel[(ch + 1) % I2S_NUM_DMA_CHANNELS]);
uint8_t *dma_buffer = self->dma_buffer + (self->sizeof_half_dma_buffer_in_bytes * ch);
if (self->mode == TX) {
channel_config_set_dreq(&dma_config, pio_get_dreq(self->pio, self->sm, true));
channel_config_set_read_increment(&dma_config, true);
channel_config_set_write_increment(&dma_config, false);
dma_channel_configure(self->dma_channel[ch],
&dma_config,
(void *)&self->pio->txf[self->sm], // dest = PIO TX FIFO
dma_buffer, // src = DMA buffer
self->sizeof_half_dma_buffer_in_bytes / (dma_get_bits(self->mode, self->bits) / 8),
false);
} else { // RX
channel_config_set_dreq(&dma_config, pio_get_dreq(self->pio, self->sm, false));
channel_config_set_read_increment(&dma_config, false);
channel_config_set_write_increment(&dma_config, true);
dma_channel_configure(self->dma_channel[ch],
&dma_config,
dma_buffer, // dest = DMA buffer
(void *)&self->pio->rxf[self->sm], // src = PIO RX FIFO
self->sizeof_half_dma_buffer_in_bytes / (dma_get_bits(self->mode, self->bits) / 8),
false);
}
}
for (uint8_t ch = 0; ch < I2S_NUM_DMA_CHANNELS; ch++) {
dma_irqn_acknowledge_channel(self->i2s_id, self->dma_channel[ch]); // clear pending. e.g. from SPI
dma_irqn_set_channel_enabled(self->i2s_id, self->dma_channel[ch], true);
}
return 0;
}
STATIC void dma_deinit(machine_i2s_obj_t *self) {
for (uint8_t ch = 0; ch < I2S_NUM_DMA_CHANNELS; ch++) {
int channel = self->dma_channel[ch];
// unchain the channel to prevent triggering a transfer in the chained-to channel
dma_channel_config dma_config = dma_get_channel_config(channel);
channel_config_set_chain_to(&dma_config, channel);
dma_channel_set_config(channel, &dma_config, false);
dma_irqn_set_channel_enabled(self->i2s_id, channel, false);
dma_channel_abort(channel); // in case a transfer is in flight
dma_channel_unclaim(channel);
}
}
STATIC void dma_irq_handler(uint8_t irq_index) {
int dma_channel = dma_map_irq_to_channel(irq_index);
if (dma_channel == -1) {
// This should never happen
return;
}
machine_i2s_obj_t *self = machine_i2s_obj[irq_index];
if (self == NULL) {
// This should never happen
return;
}
uint8_t *dma_buffer = dma_get_buffer(self, dma_channel);
if (dma_buffer == NULL) {
// This should never happen
return;
}
if (self->mode == TX) {
// for non-blocking operation handle the write() method requests.
if ((self->io_mode == NON_BLOCKING) && (self->non_blocking_descriptor.copy_in_progress)) {
copy_appbuf_to_ringbuf_non_blocking(self);
}
uint32_t trans_count = feed_dma(self, dma_buffer);
//dma_channel_set_trans_count (dma_channel, trans_count, false);
dma_channel_set_read_addr(dma_channel, dma_buffer, false);
dma_irqn_acknowledge_channel(irq_index, dma_channel);
} else { // RX
empty_dma(self, dma_buffer);
dma_irqn_acknowledge_channel(irq_index, dma_channel);
dma_channel_set_write_addr(dma_channel, dma_buffer, false);
// for non-blocking operation handle the readinto() method requests.
if ((self->io_mode == NON_BLOCKING) && (self->non_blocking_descriptor.copy_in_progress)) {
fill_appbuf_from_ringbuf_non_blocking(self);
}
}
}
STATIC void dma_irq0_handler(void) {
dma_irq_handler(0);
}
STATIC void dma_irq1_handler(void) {
dma_irq_handler(1);
}
STATIC int machine_i2s_init_helper(machine_i2s_obj_t *self,
mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd,
i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format,
int32_t ring_buffer_len, int32_t i2s_rate) {
//
// ---- Check validity of arguments ----
//
// does WS pin follow SCK pin?
// note: SCK and WS are implemented as PIO sideset pins. Sideset pins must be sequential.
if (ws != (sck + 1)) {
//mp_raise_ValueError(MP_ERROR_TEXT("invalid ws (must be sck+1)"));
return -1;
}
// is Mode valid?
if ((i2s_mode != RX) &&
(i2s_mode != TX)) {
//mp_raise_ValueError(MP_ERROR_TEXT("invalid mode"));
return -2;
}
// is Bits valid?
if ((i2s_bits != 16) &&
(i2s_bits != 32)) {
//mp_raise_ValueError(MP_ERROR_TEXT("invalid bits"));
return -3;
}
// is Format valid?
if ((i2s_format != MONO) &&
(i2s_format != STEREO)) {
//mp_raise_ValueError(MP_ERROR_TEXT("invalid format"));
return -4;
}
// is Rate valid?
// Not checked
// is Ibuf valid?
if (ring_buffer_len > 0) {
self->ring_buffer_storage = m_new(uint8_t, ring_buffer_len);
;
ringbuf_init(&self->ring_buffer, self->ring_buffer_storage, ring_buffer_len);
} else {
//mp_raise_ValueError(MP_ERROR_TEXT("invalid ibuf"));
return -5;
}
self->sck = sck;
self->ws = ws;
self->sd = sd;
self->mode = i2s_mode;
self->bits = i2s_bits;
self->format = i2s_format;
self->rate = i2s_rate;
self->ibuf = ring_buffer_len;
self->non_blocking_descriptor.copy_in_progress = false;
self->io_mode = BLOCKING;
//memset(self->dma_buffer, 0, SIZEOF_DMA_BUFFER_IN_BYTES);
self->sizeof_half_dma_buffer_in_bytes = ((self->rate+999)/1000) * ((i2s_bits == 32) ? 8 : 4);
self->sizeof_non_blocking_copy_in_bytes = self->sizeof_half_dma_buffer_in_bytes * NON_BLOCKING_RATE_MULTIPLIER;
irq_configure(self);
int err = pio_configure(self);
if (err != 0) {
return err;
}
gpio_configure(self);
err = dma_configure(self);
if (err != 0) {
return err;
}
pio_sm_set_enabled(self->pio, self->sm, true);
dma_channel_start(self->dma_channel[0]);
return 0;
}
// STATIC machine_i2s_obj_t *mp_machine_i2s_make_new_instance(mp_int_t i2s_id) {
// if (i2s_id >= MAX_I2S_RP2) {
// mp_raise_ValueError(MP_ERROR_TEXT("invalid id"));
// }
// machine_i2s_obj_t *self;
// if (MP_STATE_PORT(machine_i2s_obj[i2s_id]) == NULL) {
// self = mp_obj_malloc(machine_i2s_obj_t, &machine_i2s_type);
// MP_STATE_PORT(machine_i2s_obj[i2s_id]) = self;
// self->i2s_id = i2s_id;
// } else {
// self = MP_STATE_PORT(machine_i2s_obj[i2s_id]);
// machine_i2s_deinit(self);
// }
// return self;
// }
STATIC machine_i2s_obj_t* machine_i2s_make_new(uint8_t i2s_id,
mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd,
i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format,
int32_t ring_buffer_len, int32_t i2s_rate) {
if (i2s_id >= MAX_I2S_RP2) {
return NULL;
}
machine_i2s_obj_t *self;
// Deinit a machine if it already created
if (machine_i2s_obj[i2s_id] != NULL) {
self = machine_i2s_obj[i2s_id];
machine_i2s_deinit(self);
}
self = m_new_obj(machine_i2s_obj_t);
machine_i2s_obj[i2s_id] = self;
self->i2s_id = i2s_id;
if (machine_i2s_init_helper(self, sck, ws, sd, i2s_mode, i2s_bits,
i2s_format, ring_buffer_len, i2s_rate) != 0) {
return NULL;
}
return self;
}
STATIC void machine_i2s_deinit(machine_i2s_obj_t *self){
// use self->pio as in indication that I2S object has already been de-initialized
if (self != NULL) {
pio_deinit(self);
dma_deinit(self);
irq_deinit(self);
self->pio = NULL; // flag object as de-initialized
machine_i2s_obj[self->i2s_id] == NULL;
free(self->ring_buffer_storage);
free(self);
}
}
STATIC int machine_i2s_stream_read(machine_i2s_obj_t *self, void *buf_in, size_t size) {
if (self->mode != RX) {
return -1;
}
uint8_t appbuf_sample_size_in_bytes = (self->bits / 8) * (self->format == STEREO ? 2: 1);
if (size % appbuf_sample_size_in_bytes != 0) {
return -2;
}
if (size == 0) {
return 0;
}
mp_buffer_info_t appbuf;
appbuf.buf = (void *)buf_in;
appbuf.len = size;
uint32_t num_bytes_read = fill_appbuf_from_ringbuf(self, &appbuf);
return num_bytes_read;
}
STATIC int machine_i2s_stream_write(machine_i2s_obj_t *self, void *buf_in, size_t size) {
if (self->mode != TX) {
return -1;
}
uint8_t appbuf_sample_size_in_bytes = (self->bits / 8) * (self->format == STEREO ? 2: 1);
if (size % appbuf_sample_size_in_bytes != 0) {
return -2;
}
if (size == 0) {
return 0;
}
mp_buffer_info_t appbuf;
appbuf.buf = (void *)buf_in;
appbuf.len = size;
uint32_t num_bytes_write = copy_appbuf_to_ringbuf(self, &appbuf);
return num_bytes_write;
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
machine_i2s_obj_t* create_machine_i2s(uint8_t i2s_id,
mp_hal_pin_obj_t sck, mp_hal_pin_obj_t ws, mp_hal_pin_obj_t sd,
i2s_mode_t i2s_mode, int8_t i2s_bits, format_t i2s_format,
int32_t ring_buffer_len, int32_t i2s_rate)
{
return machine_i2s_make_new(i2s_id, sck, ws, sd, i2s_mode, i2s_bits, i2s_format, ring_buffer_len, i2s_rate);
}
int machine_i2s_read_stream(machine_i2s_obj_t *self, void *buf_in, size_t size)
{
return machine_i2s_stream_read(self, buf_in, size);
}
int machine_i2s_write_stream(machine_i2s_obj_t *self, void *buf_in, size_t size){
return machine_i2s_stream_write(self, buf_in, size);
}
// void update_pio_frequency(machine_i2s_obj_t *self, uint32_t sample_freq) {
// uint8_t dma_bits = dma_get_bits(self->mode, self->bits);
// uint32_t system_clock_frequency = clock_get_hz(clk_sys);
// assert(system_clock_frequency < 0x40000000);
// uint32_t divider = system_clock_frequency * ((dma_bits == 32) ? 2 : 4) / sample_freq; // avoid arithmetic overflow
// assert(divider < 0x1000000);
// pio_sm_set_clkdiv_int_frac(self->pio, self->sm, divider >> 8u, divider & 0xffu);
// self->rate = sample_freq;
// self->sizeof_half_dma_buffer_in_bytes = ((self->rate+999)/1000) * ((self->bits == 32) ? 8 : 4);
// self->sizeof_non_blocking_copy_in_bytes = self->sizeof_half_dma_buffer_in_bytes * NON_BLOCKING_RATE_MULTIPLIER;
// }
\ No newline at end of file
/*
* Copyright (c) 2021 Arm Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include "hardware/pio.h"
#include "hardware/clocks.h"
#include "hardware/gpio.h"
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "pdm_microphone.pio.h"
#include "pico/pdm_microphone.h"
STATIC pdm_mic_obj* pdm_mic_inst[MAX_PDM_RP2] = {NULL, NULL};
STATIC const PIO pio_instances[NUM_PIOS] = {pio0, pio1};
void pdm_dma_handler(uint8_t pdm_id);
STATIC void pdm_dma_irq0_handler();
STATIC void pdm_dma_irq1_handler();
pdm_mic_obj* pdm_microphone_init(pdm_microphone_config* config) {
uint8_t pdm_id = config->pdm_id;
if(pdm_id >= MAX_PDM_RP2)
return NULL;
if (config->sample_buffer_size % (config->sample_rate / 1000)) {
return NULL;
}
pdm_mic_obj* pdm_mic;
if(pdm_mic_inst[pdm_id] == NULL){
pdm_mic = m_new_obj(pdm_mic_obj);
pdm_mic_inst[pdm_id] = pdm_mic;
} else {
pdm_mic = pdm_mic_inst[pdm_id];
pdm_microphone_deinit(pdm_mic);
}
memset(pdm_mic, 0x00, sizeof(pdm_mic));
pdm_mic->config = config;
pdm_mic->pdm_id = pdm_id;
// if(pdm_mic->pdm_id == 0)
// {
// pdm_mic->config->dma_irq = DMA_IRQ_0;
// }
// else
// {
// pdm_mic->config->dma_irq = DMA_IRQ_1;
// }
// pdm_mic->config->dma_irq = DMA_IRQ_0;
pdm_mic->raw_buffer_size = config->sample_buffer_size * (PDM_DECIMATION / 8);
for (int i = 0; i < PDM_RAW_BUFFER_COUNT; i++) {
pdm_mic->raw_buffer[i] = malloc(pdm_mic->raw_buffer_size);
if (pdm_mic->raw_buffer[i] == NULL) {
pdm_microphone_deinit(pdm_mic);
return NULL;
}
}
pdm_mic->dma_channel = dma_claim_unused_channel(true);
if (pdm_mic->dma_channel < 0) {
pdm_microphone_deinit(pdm_mic);
return NULL;
}
pdm_mic->pio_sm_offset = pio_add_program(pdm_mic->config->pio, &pdm_microphone_data_program);
pdm_mic->pio_sm = pio_claim_unused_sm(pdm_mic->config->pio, true);
float clk_div = clock_get_hz(clk_sys) / (config->sample_rate * PDM_DECIMATION * 4.0);
pdm_microphone_data_init(
pdm_mic->config->pio,
pdm_mic->pio_sm,
pdm_mic->pio_sm_offset,
clk_div,
config->gpio_data,
config->gpio_clk
);
dma_channel_config dma_channel_cfg = dma_channel_get_default_config(pdm_mic->dma_channel);
channel_config_set_transfer_data_size(&dma_channel_cfg, DMA_SIZE_8);
channel_config_set_read_increment(&dma_channel_cfg, false);
channel_config_set_write_increment(&dma_channel_cfg, true);
channel_config_set_dreq(&dma_channel_cfg, pio_get_dreq(pdm_mic->config->pio, pdm_mic->pio_sm, false));
dma_channel_configure(
pdm_mic->dma_channel,
&dma_channel_cfg,
pdm_mic->raw_buffer[0],
(void *)&pdm_mic->config->pio->rxf[pdm_mic->pio_sm],
pdm_mic->raw_buffer_size,
false
);
pdm_mic->filter.Fs = config->sample_rate;
pdm_mic->filter.LP_HZ = config->sample_rate / 2;
pdm_mic->filter.HP_HZ = 10;
pdm_mic->filter.In_MicChannels = 1;
pdm_mic->filter.Out_MicChannels = 1;
pdm_mic->filter.Decimation = PDM_DECIMATION;
pdm_mic->filter.MaxVolume = 64;
pdm_mic->filter.Gain = 16;
pdm_mic->filter_volume = pdm_mic->filter.MaxVolume;
pdm_mic->raw_buffer_write_index = 0;
pdm_mic->raw_buffer_read_index = 0;
return pdm_mic;
}
void pdm_microphone_deinit(pdm_mic_obj *pdm_mic) {
for (int i = 0; i < PDM_RAW_BUFFER_COUNT; i++) {
if (pdm_mic->raw_buffer[i]) {
free(pdm_mic->raw_buffer[i]);
pdm_mic->raw_buffer[i] = NULL;
}
}
if (pdm_mic->dma_channel > -1) {
dma_channel_unclaim(pdm_mic->dma_channel);
pdm_mic->dma_channel = -1;
}
pdm_mic_inst[pdm_mic->pdm_id] == NULL;
}
static int irq0_handler_cntr = 0;
static int irq1_handler_cntr = 0;
int pdm_microphone_start(pdm_mic_obj *pdm_mic) {
irq_set_enabled(pdm_mic->config->dma_irq, true);
dma_irqn_acknowledge_channel(pdm_mic->config->dma_irq, pdm_mic->dma_channel);
dma_irqn_set_channel_enabled(pdm_mic->config->dma_irq, pdm_mic->dma_channel, true);
if(pdm_mic->config->dma_irq == DMA_IRQ_0)
{
dma_channel_set_irq0_enabled(pdm_mic->dma_channel, true);
if(irq0_handler_cntr == 0)
{
irq_set_exclusive_handler(pdm_mic->config->dma_irq, pdm_dma_irq0_handler);
}
irq0_handler_cntr++;
}
else
{
dma_channel_set_irq1_enabled(pdm_mic->dma_channel, true);
if(irq1_handler_cntr == 0)
{
irq_set_exclusive_handler(pdm_mic->config->dma_irq, pdm_dma_irq1_handler);
}
irq1_handler_cntr++;
}
Open_PDM_Filter_Init(&pdm_mic->filter);
pio_sm_set_enabled(
pdm_mic->config->pio,
pdm_mic->pio_sm,
true
);
pdm_mic->raw_buffer_write_index = 0;
pdm_mic->raw_buffer_read_index = 0;
//-------- Strange workaround ------------
dma_channel_transfer_to_buffer_now(
pdm_mic->dma_channel,
pdm_mic->raw_buffer[0],
pdm_mic->raw_buffer_size
);
//----------------------------------------
return 0;
}
void pdm_microphone_stop(pdm_mic_obj *pdm_mic) {
pio_sm_set_enabled(
pdm_mic->config->pio,
pdm_mic->pio_sm,
false
);
dma_channel_abort(pdm_mic->dma_channel);
if (pdm_mic->config->dma_irq == DMA_IRQ_0) {
dma_channel_set_irq0_enabled(pdm_mic->dma_channel, false);
} else if (pdm_mic->config->dma_irq == DMA_IRQ_1) {
dma_channel_set_irq1_enabled(pdm_mic->dma_channel, false);
}
irq_set_enabled(pdm_mic->config->dma_irq, false);
}
STATIC uint dma_map_irq_to_channel(uint irq_index) {
for (uint ch = 0; ch < NUM_DMA_CHANNELS; ch++) {
if ((dma_irqn_get_channel_status(irq_index, ch))) {
return ch;
}
}
// This should never happen
return -1;
}
STATIC void pdm_dma_handle_mic(pdm_mic_obj *pdm_mic)
{
// get the next capture index to send the dma to start
pdm_mic->raw_buffer_read_index = pdm_mic->raw_buffer_write_index;
pdm_mic->raw_buffer_write_index = (pdm_mic->raw_buffer_write_index + 1) % PDM_RAW_BUFFER_COUNT;
// give the channel a new buffer to write to and re-trigger it
dma_channel_transfer_to_buffer_now(
pdm_mic->dma_channel,
pdm_mic->raw_buffer[pdm_mic->raw_buffer_write_index],
pdm_mic->raw_buffer_size
);
// dma_channel_set_write_addr(
// pdm_mic->dma_channel,
// pdm_mic->raw_buffer[pdm_mic->raw_buffer_write_index],
// false);
if (pdm_mic->samples_ready_handler) {
pdm_mic->samples_ready_handler(pdm_mic->pdm_id);
}
}
void pdm_dma_handler(uint8_t irq_index) {
for(uint pdm_idx =0; pdm_idx < MAX_PDM_RP2; pdm_idx++) {
if((pdm_mic_inst[pdm_idx] != NULL) && (pdm_mic_inst[pdm_idx]->config->dma_irq == irq_index)){
uint ch = pdm_mic_inst[pdm_idx]->dma_channel;
if ((dma_irqn_get_channel_status(irq_index, ch))) {
pdm_mic_obj *pdm_mic = pdm_mic_inst[pdm_idx];
pdm_dma_handle_mic(pdm_mic);
// clear IRQ
dma_irqn_acknowledge_channel(irq_index, ch);
}
}
}
}
STATIC void pdm_dma_irq0_handler()
{
pdm_dma_handler(DMA_IRQ_0);
}
STATIC void pdm_dma_irq1_handler()
{
pdm_dma_handler(DMA_IRQ_1);
}
void pdm_microphone_set_samples_ready_handler(pdm_mic_obj *pdm_mic, pdm_samples_ready_handler_t handler) {
pdm_mic->samples_ready_handler = handler;
}
void pdm_microphone_set_filter_max_volume(pdm_mic_obj *pdm_mic, uint8_t max_volume) {
pdm_mic->filter.MaxVolume = max_volume;
}
void pdm_microphone_set_filter_gain(pdm_mic_obj *pdm_mic, uint8_t gain) {
pdm_mic->filter.Gain = gain;
}
void pdm_microphone_set_filter_volume(pdm_mic_obj *pdm_mic, uint16_t volume) {
pdm_mic->filter_volume = volume;
}
int pdm_microphone_read(pdm_mic_obj *pdm_mic, int16_t* buffer, size_t samples) {
int filter_stride = (pdm_mic->filter.Fs / 1000);
samples = (samples / filter_stride) * filter_stride;
if (samples > pdm_mic->config->sample_buffer_size) {
samples = pdm_mic->config->sample_buffer_size;
}
if (pdm_mic->raw_buffer_write_index == pdm_mic->raw_buffer_read_index) {
return 0;
}
uint8_t* in = pdm_mic->raw_buffer[pdm_mic->raw_buffer_read_index];
int16_t* out = buffer;
// get the current buffer index
pdm_mic->raw_buffer_read_index = (pdm_mic->raw_buffer_read_index + 1) % PDM_RAW_BUFFER_COUNT;
for (int i = 0; i < samples; i += filter_stride) {
#if PDM_DECIMATION == 64
Open_PDM_Filter_64(in, out, pdm_mic->filter_volume, &pdm_mic->filter);
#elif PDM_DECIMATION == 128
Open_PDM_Filter_128(in, out, pdm_mic->filter_volume, &pdm_mic->filter);
#else
#error "Unsupported PDM_DECIMATION value!"
#endif
in += filter_stride * (PDM_DECIMATION / 8);
out += filter_stride;
}
return samples;
}
/*
* Copyright (c) 2021 Arm Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
.program pdm_microphone_data
.side_set 1
.wrap_target
nop side 0
in pins, 1 side 0
push iffull noblock side 1
nop side 1
.wrap
% c-sdk {
static inline void pdm_microphone_data_init(PIO pio, uint sm, uint offset, float clk_div, uint data_pin, uint clk_pin) {
pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, false);
pio_sm_set_consecutive_pindirs(pio, sm, clk_pin, 1, true);
pio_gpio_init(pio, clk_pin);
pio_gpio_init(pio, data_pin);
//gpio_pull_up(pin); //?????
pio_sm_config c = pdm_microphone_data_program_get_default_config(offset);
sm_config_set_in_pins(&c, data_pin); // Data in pi
sm_config_set_sideset_pins(&c, clk_pin); // Clock controlled by side set
// Shift to left, autopush disabled
sm_config_set_in_shift(&c, false, false, 8);
// Join RX channed to have deeper fifo. TX is not used
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
// Set clock divider
sm_config_set_clkdiv(&c, clk_div);
pio_sm_init(pio, sm, offset, &c);
// Need to call from app to sync microphones
//pio_sm_set_enabled(pio, sm, true);
}
%}
#include "pico/ring_buf.h"
// Ring Buffer
// Thread safe when used with these constraints:
// - Single Producer, Single Consumer
// - Sequential atomic operations
// One byte of capacity is used to detect buffer empty/full
void ringbuf_init(ring_buf_t *rbuf, uint8_t *buffer, size_t size) {
rbuf->buffer = buffer;
rbuf->size = size;
rbuf->head = 0;
rbuf->tail = 0;
}
bool ringbuf_push(ring_buf_t *rbuf, uint8_t data) {
size_t next_tail = (rbuf->tail + 1) % rbuf->size;
if (next_tail != rbuf->head) {
rbuf->buffer[rbuf->tail] = data;
rbuf->tail = next_tail;
return true;
}
// full
return false;
}
bool ringbuf_pop(ring_buf_t *rbuf, uint8_t *data) {
stdio_flush();
if (rbuf->head == rbuf->tail) {
// empty
return false;
}
*data = rbuf->buffer[rbuf->head];
rbuf->head = (rbuf->head + 1) % rbuf->size;
return true;
}
bool ringbuf_is_empty(ring_buf_t *rbuf) {
return rbuf->head == rbuf->tail;
}
bool ringbuf_is_full(ring_buf_t *rbuf) {
return ((rbuf->tail + 1) % rbuf->size) == rbuf->head;
}
size_t ringbuf_available_data(ring_buf_t *rbuf) {
return (rbuf->tail - rbuf->head + rbuf->size) % rbuf->size;
}
size_t ringbuf_available_space(ring_buf_t *rbuf) {
return rbuf->size - ringbuf_available_data(rbuf) - 1;
}
\ No newline at end of file
#include "pico/volume_ctrl.h"
uint16_t vol_to_db_convert(bool channel_mute, uint16_t channel_volume){
if(channel_mute)
return 0;
// todo interpolate
channel_volume += CENTER_VOLUME_INDEX * 256;
if (channel_volume < 0) channel_volume = 0;
if (channel_volume >= count_of(db_to_vol) * 256) channel_volume = count_of(db_to_vol) * 256 - 1;
uint16_t vol_mul = db_to_vol[((uint16_t)channel_volume) >> 8u];
return vol_mul;
}
\ No newline at end of file
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.2.0)
set(toolchainVersion 14_2_Rel1)
set(picotoolVersion 2.2.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
cmake_minimum_required(VERSION 3.13)
set(PICO_BOARD pico_w CACHE STRING "Board type")
set(PROGRAM_NAME imu_data_logger)
include(pico_sdk_import.cmake)
project(${PROGRAM_NAME} C CXX ASM)
# Initialise the Pico SDK
pico_sdk_init()
add_subdirectory(sd_card_driver build)
# Add executable. Default name is the project name, version 0.1
add_executable(${PROGRAM_NAME}
config/hw_config.c
main.cpp
)
# https://datasheets.raspberrypi.com/pico/raspberry-pi-pico-c-sdk.pdf
target_compile_definitions(${PROGRAM_NAME} PRIVATE
PICO_STACK_SIZE=0x1000
PICO_CORE1_STACK_SIZE=0x800
)
target_compile_options(${PROGRAM_NAME} PUBLIC
-Wall
-Wextra
-Wshadow
-Wstack-usage=2048
-fanalyzer
)
add_compile_definitions(
PARAM_ASSERTIONS_ENABLE_ALL=1
PICO_MALLOC_PANIC=1
PICO_USE_STACK_GUARDS=1
)
set_property(TARGET ${PROGRAM_NAME} APPEND_STRING PROPERTY LINK_FLAGS
"-Wl,--print-memory-usage"
)
pico_set_program_name(${PROGRAM_NAME} "${PROGRAM_NAME}")
pico_set_program_version(${PROGRAM_NAME} "3.3.1")
pico_enable_stdio_usb(${PROGRAM_NAME} 1)
target_include_directories(${PROGRAM_NAME} PUBLIC
include/
)
target_link_libraries(${PROGRAM_NAME}
sd_custom_driver
hardware_clocks
hardware_adc
)
pico_add_extra_outputs(${PROGRAM_NAME})
/* hw_config.c
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
/*
This file should be tailored to match the hardware design.
There should be one element of the spi[] array for each RP2040 hardware SPI used.
There should be one element of the spi_ifs[] array for each SPI interface object.
* Each element of spi_ifs[] must point to an spi_t instance with member "spi".
There should be one element of the sdio_ifs[] array for each SDIO interface object.
There should be one element of the sd_cards[] array for each SD card slot.
* Each element of sd_cards[] must point to its interface with spi_if_p or sdio_if_p.
*/
/* Hardware configuration for Pico SD Card Development Board
See https://oshwlab.com/carlk3/rp2040-sd-card-dev
See https://docs.google.com/spreadsheets/d/1BrzLWTyifongf_VQCc2IpJqXWtsrjmG7KnIbSBy-CPU/edit?usp=sharing,
tab "Dev Brd", for pin assignments assumed in this configuration file.
*/
#include <assert.h>
//
#include "hw_config.h"
// Hardware Configuration of SPI "objects"
// Note: multiple SD cards can be driven by one SPI if they use different slave
// selects (or "chip selects").
static spi_t spis[] = { // One for each RP2040 SPI component used
{ // spis[0]
.hw_inst = spi0, // RP2040 SPI component
.sck_gpio = 5, // GPIO number (not Pico pin number)
.mosi_gpio = 18,
.miso_gpio = 19,
.set_drive_strength = true,
.mosi_gpio_drive_strength = GPIO_DRIVE_STRENGTH_2MA,
.sck_gpio_drive_strength = GPIO_DRIVE_STRENGTH_12MA,
.no_miso_gpio_pull_up = true,
// .baud_rate = 125 * 1000 * 1000 / 10 // 12500000 Hz
// .baud_rate = 125 * 1000 * 1000 / 8 // 15625000 Hz
.baud_rate = 125 * 1000 * 1000 / 6 // 20833333 Hz
// .baud_rate = 125 * 1000 * 1000 / 4 // 31250000 Hz
},
{ // spis[1]
.hw_inst = spi1, // RP2040 SPI component
.miso_gpio = 5, // GPIO number (not Pico pin number)
.sck_gpio = 18,
.mosi_gpio = 19,
.set_drive_strength = true,
.mosi_gpio_drive_strength = GPIO_DRIVE_STRENGTH_4MA,
.sck_gpio_drive_strength = GPIO_DRIVE_STRENGTH_12MA,
.no_miso_gpio_pull_up = true,
//.baud_rate = 125 * 1000 * 1000 / 12
//.baud_rate = 125 * 1000 * 1000 / 10 // 12500000 Hz
//.baud_rate = 125 * 1000 * 1000 / 8 // 15625000 Hz
.baud_rate = 125 * 1000 * 1000 / 6 // 20833333 Hz
// .baud_rate = 125 * 1000 * 1000 / 4 // 31250000 Hz
}
};
/* SPI Interfaces */
static sd_spi_if_t spi_ifs[] = {
{ // spi_ifs[0]
.spi = &spis[0], // Pointer to the SPI driving this card
.ss_gpio = 22, // The SPI slave select GPIO for this SD card
.set_drive_strength = true,
.ss_gpio_drive_strength = GPIO_DRIVE_STRENGTH_2MA
},
{ // spi_ifs[1]
.spi = &spis[1], // Pointer to the SPI driving this card
.ss_gpio = 22, // The SPI slave select GPIO for this SD card
.set_drive_strength = true,
.ss_gpio_drive_strength = GPIO_DRIVE_STRENGTH_2MA
},
{ // spi_ifs[2]
.spi = &spis[1], // Pointer to the SPI driving this card
.ss_gpio = 22, // The SPI slave select GPIO for this SD card
.set_drive_strength = true,
.ss_gpio_drive_strength = GPIO_DRIVE_STRENGTH_2MA
}
};
/* SDIO Interfaces */
/*
Pins CLK_gpio, D1_gpio, D2_gpio, and D3_gpio are at offsets from pin D0_gpio.
The offsets are determined by sd_driver\SDIO\rp2040_sdio.pio.
CLK_gpio = (D0_gpio + SDIO_CLK_PIN_D0_OFFSET) % 32;
As of this writing, SDIO_CLK_PIN_D0_OFFSET is 30,
which is -2 in mod32 arithmetic, so:
CLK_gpio = D0_gpio -2.
D1_gpio = D0_gpio + 1;
D2_gpio = D0_gpio + 2;
D3_gpio = D0_gpio + 3;
*/
static sd_sdio_if_t sdio_ifs[] = {
{ // sdio_ifs[0]
.CMD_gpio = 18,
.D0_gpio = 19,
.CLK_gpio_drive_strength = GPIO_DRIVE_STRENGTH_12MA,
.CMD_gpio_drive_strength = GPIO_DRIVE_STRENGTH_4MA,
.D0_gpio_drive_strength = GPIO_DRIVE_STRENGTH_4MA,
.D1_gpio_drive_strength = GPIO_DRIVE_STRENGTH_4MA,
.D2_gpio_drive_strength = GPIO_DRIVE_STRENGTH_4MA,
.D3_gpio_drive_strength = GPIO_DRIVE_STRENGTH_4MA,
.SDIO_PIO = pio1,
.DMA_IRQ_num = DMA_IRQ_1,
// .baud_rate = 125 * 1000 * 1000 / 8 // 15625000 Hz
.baud_rate = 125 * 1000 * 1000 / 7 // 17857143 Hz
// .baud_rate = 125 * 1000 * 1000 / 6 // 20833333 Hz
// .baud_rate = 125 * 1000 * 1000 / 5 // 25000000 Hz
// .baud_rate = 125 * 1000 * 1000 / 4 // 31250000 Hz
},
{ // sdio_ifs[1]
.CMD_gpio = 18,
.D0_gpio = 19,
.CLK_gpio_drive_strength = GPIO_DRIVE_STRENGTH_12MA,
.CMD_gpio_drive_strength = GPIO_DRIVE_STRENGTH_4MA,
.D0_gpio_drive_strength = GPIO_DRIVE_STRENGTH_4MA,
.D1_gpio_drive_strength = GPIO_DRIVE_STRENGTH_4MA,
.D2_gpio_drive_strength = GPIO_DRIVE_STRENGTH_4MA,
.D3_gpio_drive_strength = GPIO_DRIVE_STRENGTH_4MA,
.DMA_IRQ_num = DMA_IRQ_1,
// .baud_rate = 125 * 1000 * 1000 / 8 // 15625000 Hz
.baud_rate = 125 * 1000 * 1000 / 7 // 17857143 Hz
// .baud_rate = 125 * 1000 * 1000 / 6 // 20833333 Hz
// .baud_rate = 125 * 1000 * 1000 / 5 // 25000000 Hz
//.baud_rate = 125 * 1000 * 1000 / 4 // 31250000 Hz
}
};
/* Hardware Configuration of the SD Card "objects"
These correspond to SD card sockets
*/
static sd_card_t sd_cards[] = { // One for each SD card
#ifdef SPI_SD0
{ // sd_cards[0]: Socket sd0
.type = SD_IF_SPI,
.spi_if_p = &spi_ifs[0], // Pointer to the SPI interface driving this card
// SD Card detect:
.use_card_detect = true,
.card_detect_gpio = 23,
.card_detected_true = 0, // What the GPIO read returns when a card is
// present.
.card_detect_use_pull = true,
.card_detect_pull_hi = true
},
#else
{ // sd_cards[0]: Socket sd0
.type = SD_IF_SDIO,
.sdio_if_p = &sdio_ifs[0], // Pointer to the SPI interface driving this card
// SD Card detect:
.use_card_detect = false,
.card_detect_gpio = 9,
.card_detected_true = 0, // What the GPIO read returns when a card is
// present.
.card_detect_use_pull = true,
.card_detect_pull_hi = true
},
#endif
{ // sd_cards[1]: Socket sd1
.type = SD_IF_SPI,
.spi_if_p = &spi_ifs[1], // Pointer to the SPI interface driving this card
// SD Card detect:
.use_card_detect = false,
.card_detect_gpio = 14,
.card_detected_true = 0, // What the GPIO read returns when a card is
// present.
.card_detect_use_pull = true,
.card_detect_pull_hi = true
},
{ // sd_cards[2]: Socket sd2
.type = SD_IF_SPI,
.spi_if_p = &spi_ifs[2], // Pointer to the SPI interface driving this card
// SD Card detect:
.use_card_detect = false,
.card_detect_gpio = 15,
.card_detected_true = 0, // What the GPIO read returns when a card is
// present.
.card_detect_use_pull = true,
.card_detect_pull_hi = true
},
{ // sd_cards[3]: Socket sd3
.type = SD_IF_SDIO,
.sdio_if_p = &sdio_ifs[1], // Pointer to the interface driving this card
// SD Card detect:
.use_card_detect = false,
.card_detect_gpio = 22,
.card_detected_true = 0, // What the GPIO read returns when a card is
// present.
.card_detect_use_pull = true,
.card_detect_pull_hi = true
}
};
/* ********************************************************************** */
size_t sd_get_num() { return count_of(sd_cards); }
sd_card_t *sd_get_by_num(size_t num) {
assert(num < sd_get_num());
if (num < sd_get_num()) {
return &sd_cards[num];
} else {
return NULL;
}
}
/* [] END OF FILE */
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "ff.h"
#include "sd_card.h"
#include "f_util.h"
#include "hw_config.h"
#define PATH_MAX_LEN 256
// --------- Globals (FatFs requires the FS to outlive the mount) ----------
static FATFS fs; // must be static/global (lives as long as the mount)
static sd_card_t *g_sd = NULL; // active SD card
static const char *g_drive = NULL; // typically "0:"
// ------------------------- Utility / Error -------------------------------
static void die(FRESULT fr, const char *op) {
printf("%s failed: %s (%d)\n", op, FRESULT_str(fr), fr);
while (1) tight_loop_contents();
}
static void loop_forever_msg(const char *msg) {
printf("%s\n", msg);
while (1) tight_loop_contents();
}
static void join_path(char *out, size_t out_sz, const char *drive, const char *rel) {
// drive = "0:" or "0:/", ensure exactly one slash when joining
if (rel && rel[0] == '/') rel++; // avoid double slashes
if (drive && drive[strlen(drive) - 1] == '/')
snprintf(out, out_sz, "%s%s", drive, rel ? rel : "");
else
snprintf(out, out_sz, "%s/%s", drive, rel ? rel : "");
}
// ------------------------- 1) Initialization -----------------------------
static bool sd_init_and_mount(void) {
if (!sd_init_driver()) {
printf("sd_init_driver() failed\n");
return false;
}
g_sd = sd_get_by_num(0);
if (!g_sd) {
printf("No SD config found (sd_get_by_num(0) == NULL)\n");
return false;
}
g_drive = sd_get_drive_prefix(g_sd); // usually "0:"
if (!g_drive) {
printf("sd_get_drive_prefix() returned NULL\n");
return false;
}
FRESULT fr = f_mount(&fs, g_drive, 1);
printf("f_mount -> %s (%d)\n", FRESULT_str(fr), fr);
if (fr == FR_NO_FILESYSTEM) {
BYTE work[4096]; // >= FF_MAX_SS
MKFS_PARM opt = { FM_FAT | FM_SFD, 0, 0, 0, 0 };
fr = f_mkfs(g_drive, &opt, work, sizeof work);
printf("f_mkfs -> %s (%d)\n", FRESULT_str(fr), fr);
if (fr == FR_OK) {
fr = f_mount(&fs, g_drive, 1);
printf("f_mount(after mkfs) -> %s (%d)\n", FRESULT_str(fr), fr);
}
}
if (fr != FR_OK) {
printf("Mount failed: %s (%d)\n", FRESULT_str(fr), fr);
return false;
}
return true;
}
// ------------------------- 2) File creation ------------------------------
static FRESULT create_file(const char *abs_path, FIL *out_file) {
// Creates/truncates a file and opens it for writing
return f_open(out_file, abs_path, FA_WRITE | FA_CREATE_ALWAYS);
}
// ------------------------- 3) File writing -------------------------------
static FRESULT write_to_file(FIL *file, const void *data, UINT len, UINT *bytes_written) {
*bytes_written = 0;
FRESULT fr = f_write(file, data, len, bytes_written);
if (fr == FR_OK) {
fr = f_sync(file); // ensure data hits the card
}
return fr;
}
// ------------------------- 4) File checking/listing ----------------------
typedef struct {
uint32_t files;
uint32_t dirs;
uint64_t total_bytes;
} list_stats_t;
static bool is_dot_or_dotdot(const char *name) {
return (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')));
}
static FRESULT list_dir_recursive(const char *path, list_stats_t *stats) {
DIR dir;
FILINFO fno;
FRESULT fr = f_opendir(&dir, path);
if (fr != FR_OK) {
printf("f_opendir('%s') -> %s (%d)\n", path, FRESULT_str(fr), fr);
return fr;
}
for (;;) {
fr = f_readdir(&dir, &fno);
if (fr != FR_OK) {
printf("f_readdir('%s') -> %s (%d)\n", path, FRESULT_str(fr), fr);
break;
}
if (fno.fname[0] == '\0') break; // end of directory
if (is_dot_or_dotdot(fno.fname)) continue;
if (fno.fattrib & AM_DIR) {
stats->dirs++;
char subpath[PATH_MAX_LEN];
snprintf(subpath, sizeof subpath, "%s/%s", path, fno.fname);
printf("[DIR] %s\n", subpath);
fr = list_dir_recursive(subpath, stats);
if (fr != FR_OK) break;
} else {
stats->files++;
stats->total_bytes += (uint64_t)fno.fsize;
printf("[FILE] %s/%s (%lu bytes)\n", path, fno.fname, (unsigned long)fno.fsize);
}
}
FRESULT frc = f_closedir(&dir);
if (fr == FR_OK && frc != FR_OK) fr = frc;
return fr;
}
// Public checker: lists all files and sizes, and tells if any exist
static FRESULT check_and_list_files(const char *root_drive) {
// Build root path "0:/"
char root[PATH_MAX_LEN];
join_path(root, sizeof root, root_drive, ""); // ensures a trailing slash when we add children
list_stats_t stats = {0};
printf("\n--- SD Card File Listing for '%s' ---\n", root_drive);
FRESULT fr = list_dir_recursive(root_drive, &stats);
if (fr != FR_OK && fr != FR_NO_PATH) {
printf("Directory listing aborted due to error.\n");
return fr;
}
if (stats.files == 0 && stats.dirs == 0) {
printf("No files or directories found on the SD card.\n");
} else if (stats.files == 0) {
printf("No files found (but %u director%s present).\n", stats.dirs, (stats.dirs == 1 ? "y" : "ies"));
} else {
printf("\nSummary: %u file%s in %u director%s, total %llu bytes.\n",
stats.files, (stats.files == 1 ? "" : "s"),
stats.dirs, (stats.dirs == 1 ? "y" : "ies"),
(unsigned long long)stats.total_bytes);
}
return FR_OK;
}
// ------------------------------ Main -------------------------------------
int main(void) {
stdio_init_all();
sleep_ms(1500);
// 1) Init + mount
if (!sd_init_and_mount()) {
loop_forever_msg("SD init/mount failed.");
}
// Build absolute file path: <drive>/test.txt
char path[PATH_MAX_LEN];
join_path(path, sizeof path, g_drive, "test1.txt");
// 2) Create the file
FIL f;
FRESULT fr = create_file(path, &f);
if (fr != FR_OK) die(fr, "f_open(create)");
// 3) Write data
const char *msg = "data writing test!\n";
UINT bw = 0;
fr = write_to_file(&f, msg, (UINT)strlen(msg), &bw);
if (fr != FR_OK || bw != strlen(msg)) die(fr, "f_write/f_sync");
printf("Wrote %u bytes to %s\n", bw, path);
// Close the file
f_close(&f);
// 4) Check and list files (recursively) on the card
fr = check_and_list_files(g_drive);
if (fr != FR_OK) die(fr, "check_and_list_files");
// Optional: unmount
fr = f_unmount(g_drive);
printf("f_unmount -> %s (%d)\n", FRESULT_str(fr), fr);
while (1) { sleep_ms(1000); }
}
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
endif ()
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
endif()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
)
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
GIT_SUBMODULES_RECURSE FALSE
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
else ()
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
endif ()
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})
add_library(sd_custom_driver INTERFACE)
pico_generate_pio_header(sd_custom_driver ${CMAKE_CURRENT_LIST_DIR}/sd_driver/SDIO/rp2040_sdio.pio)
target_compile_definitions(sd_custom_driver INTERFACE
PICO_MAX_SHARED_IRQ_HANDLERS=8u
)
# target_compile_options(sd_custom_driver INTERFACE -ffile-prefix-map=${CMAKE_CURRENT_LIST_DIR}=)
target_sources(sd_custom_driver INTERFACE
${CMAKE_CURRENT_LIST_DIR}/ff15/source/ff.c
${CMAKE_CURRENT_LIST_DIR}/ff15/source/ffsystem.c
${CMAKE_CURRENT_LIST_DIR}/ff15/source/ffunicode.c
${CMAKE_CURRENT_LIST_DIR}/sd_driver/dma_interrupts.c
${CMAKE_CURRENT_LIST_DIR}/sd_driver/sd_card.c
${CMAKE_CURRENT_LIST_DIR}/sd_driver/SDIO/rp2040_sdio.c
${CMAKE_CURRENT_LIST_DIR}/sd_driver/SDIO/sd_card_sdio.c
${CMAKE_CURRENT_LIST_DIR}/sd_driver/SPI/my_spi.c
${CMAKE_CURRENT_LIST_DIR}/sd_driver/SPI/sd_card_spi.c
${CMAKE_CURRENT_LIST_DIR}/sd_driver/SPI/sd_spi.c
${CMAKE_CURRENT_LIST_DIR}/src/crash.c
${CMAKE_CURRENT_LIST_DIR}/src/crc.c
${CMAKE_CURRENT_LIST_DIR}/src/f_util.c
${CMAKE_CURRENT_LIST_DIR}/src/ff_stdio.c
${CMAKE_CURRENT_LIST_DIR}/src/file_stream.c
${CMAKE_CURRENT_LIST_DIR}/src/glue.c
${CMAKE_CURRENT_LIST_DIR}/src/my_debug.c
${CMAKE_CURRENT_LIST_DIR}/src/my_rtc.c
${CMAKE_CURRENT_LIST_DIR}/src/sd_timeouts.c
${CMAKE_CURRENT_LIST_DIR}/src/util.c
)
target_include_directories(sd_custom_driver INTERFACE
ff15/source
sd_driver
include
)
target_link_libraries(sd_custom_driver INTERFACE
hardware_dma
hardware_pio
hardware_spi
pico_aon_timer
pico_stdlib
cmsis_core
)
/*---------------------------------------------------------------------------/
/ Configurations of FatFs Module
/---------------------------------------------------------------------------*/
#define FFCONF_DEF 80286 /* Revision ID */
/*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_READONLY 0
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
/ and optional writing functions as well. */
#define FF_FS_MINIMIZE 0
/* This option defines minimization level to remove some basic API functions.
/
/ 0: Basic functions are fully enabled.
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
/ are removed.
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
/ 3: f_lseek() function is removed in addition to 2. */
#define FF_USE_FIND 0
/* This option switches filtered directory read functions, f_findfirst() and
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
#define FF_USE_MKFS 0
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
#define FF_USE_FASTSEEK 0
/* This option switches fast seek function. (0:Disable or 1:Enable) */
#define FF_USE_EXPAND 0
/* This option switches f_expand function. (0:Disable or 1:Enable) */
#define FF_USE_CHMOD 0
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
#define FF_USE_LABEL 0
/* This option switches volume label functions, f_getlabel() and f_setlabel().
/ (0:Disable or 1:Enable) */
#define FF_USE_FORWARD 0
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
#define FF_USE_STRFUNC 0
#define FF_PRINT_LLI 1
#define FF_PRINT_FLOAT 1
#define FF_STRF_ENCODE 3
/* FF_USE_STRFUNC switches string functions, f_gets(), f_putc(), f_puts() and
/ f_printf().
/
/ 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
/ 1: Enable without LF-CRLF conversion.
/ 2: Enable with LF-CRLF conversion.
/
/ FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
/ makes f_printf() support floating point argument. These features want C99 or later.
/ When FF_LFN_UNICODE >= 1 with LFN enabled, string functions convert the character
/ encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
/ to be read/written via those functions.
/
/ 0: ANSI/OEM in current CP
/ 1: Unicode in UTF-16LE
/ 2: Unicode in UTF-16BE
/ 3: Unicode in UTF-8
*/
/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/
#define FF_CODE_PAGE 932
/* This option specifies the OEM code page to be used on the target system.
/ Incorrect code page setting can cause a file open failure.
/
/ 437 - U.S.
/ 720 - Arabic
/ 737 - Greek
/ 771 - KBL
/ 775 - Baltic
/ 850 - Latin 1
/ 852 - Latin 2
/ 855 - Cyrillic
/ 857 - Turkish
/ 860 - Portuguese
/ 861 - Icelandic
/ 862 - Hebrew
/ 863 - Canadian French
/ 864 - Arabic
/ 865 - Nordic
/ 866 - Russian
/ 869 - Greek 2
/ 932 - Japanese (DBCS)
/ 936 - Simplified Chinese (DBCS)
/ 949 - Korean (DBCS)
/ 950 - Traditional Chinese (DBCS)
/ 0 - Include all code pages above and configured by f_setcp()
*/
#define FF_USE_LFN 0
#define FF_MAX_LFN 255
/* The FF_USE_LFN switches the support for LFN (long file name).
/
/ 0: Disable LFN. FF_MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
/
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
/ be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN
/ specification.
/ When use stack for the working buffer, take care on stack overflow. When use heap
/ memory for the working buffer, memory management functions, ff_memalloc() and
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
#define FF_LFN_UNICODE 0
/* This option switches the character encoding on the API when LFN is enabled.
/
/ 0: ANSI/OEM in current CP (TCHAR = char)
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
/ 2: Unicode in UTF-8 (TCHAR = char)
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
/
/ Also behavior of string I/O functions will be affected by this option.
/ When LFN is not enabled, this option has no effect. */
#define FF_LFN_BUF 255
#define FF_SFN_BUF 12
/* This set of options defines size of file name members in the FILINFO structure
/ which is used to read out directory items. These values should be suffcient for
/ the file names to read. The maximum possible length of the read file name depends
/ on character encoding. When LFN is not enabled, these options have no effect. */
#define FF_FS_RPATH 0
/* This option configures support for relative path.
/
/ 0: Disable relative path and remove related functions.
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
/ 2: f_getcwd() function is available in addition to 1.
*/
/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/
#define FF_VOLUMES 1
/* Number of volumes (logical drives) to be used. (1-10) */
#define FF_STR_VOLUME_ID 0
#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
/ logical drives. Number of items must not be less than FF_VOLUMES. Valid
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
/ not defined, a user defined volume string table is needed as:
/
/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
*/
#define FF_MULTI_PARTITION 0
/* This option switches support for multiple volumes on the physical drive.
/ By default (0), each logical drive number is bound to the same physical drive
/ number and only an FAT volume found on the physical drive will be mounted.
/ When this function is enabled (1), each logical drive number can be bound to
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
/ function will be available. */
#define FF_MIN_SS 512
#define FF_MAX_SS 512
/* This set of options configures the range of sector size to be supported. (512,
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
/ harddisk, but a larger value may be required for on-board flash memory and some
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
/ for variable sector size mode and disk_ioctl() function needs to implement
/ GET_SECTOR_SIZE command. */
#define FF_LBA64 0
/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */
#define FF_MIN_GPT 0x10000000
/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs and
/ f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */
#define FF_USE_TRIM 0
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
/ disk_ioctl() function. */
/*---------------------------------------------------------------------------/
/ System Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_TINY 0
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
/ Instead of private sector buffer eliminated from the file object, common sector
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
#define FF_FS_EXFAT 0
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
#define FF_FS_NORTC 0
#define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2022
/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
/ an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
/ timestamp feature. Every object modified by FatFs will have a fixed timestamp
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
#define FF_FS_NOFSINFO 0
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/ option, and f_getfree() function at the first time after volume mount will force
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/ bit0=0: Use free cluster count in the FSINFO if available.
/ bit0=1: Do not trust free cluster count in the FSINFO.
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
*/
#define FF_FS_LOCK 0
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
/ is 1.
/
/ 0: Disable file lock function. To avoid volume corruption, application program
/ should avoid illegal open, remove and rename to the open objects.
/ >0: Enable file lock function. The value defines how many files/sub-directories
/ can be opened simultaneously under file lock control. Note that the file
/ lock control is independent of re-entrancy. */
#define FF_FS_REENTRANT 0
#define FF_FS_TIMEOUT 1000
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/ module itself. Note that regardless of this option, file access to different
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
/ to the same volume is under control of this featuer.
/
/ 0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
/ ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give()
/ function, must be added to the project. Samples are available in ffsystem.c.
/
/ The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
*/
/*--- End of configuration options ---*/
----------------------------------------------------------------------------
Revision history of FatFs module
----------------------------------------------------------------------------
R0.00 (February 26, 2006)
Prototype.
R0.01 (April 29, 2006)
The first release.
R0.02 (June 01, 2006)
Added FAT12 support.
Removed unbuffered mode.
Fixed a problem on small (<32M) partition.
R0.02a (June 10, 2006)
Added a configuration option (_FS_MINIMUM).
R0.03 (September 22, 2006)
Added f_rename().
Changed option _FS_MINIMUM to _FS_MINIMIZE.
R0.03a (December 11, 2006)
Improved cluster scan algorithm to write files fast.
Fixed f_mkdir() creates incorrect directory on FAT32.
R0.04 (February 04, 2007)
Added f_mkfs().
Supported multiple drive system.
Changed some interfaces for multiple drive system.
Changed f_mountdrv() to f_mount().
R0.04a (April 01, 2007)
Supported multiple partitions on a physical drive.
Added a capability of extending file size to f_lseek().
Added minimization level 3.
Fixed an endian sensitive code in f_mkfs().
R0.04b (May 05, 2007)
Added a configuration option _USE_NTFLAG.
Added FSINFO support.
Fixed DBCS name can result FR_INVALID_NAME.
Fixed short seek (<= csize) collapses the file object.
R0.05 (August 25, 2007)
Changed arguments of f_read(), f_write() and f_mkfs().
Fixed f_mkfs() on FAT32 creates incorrect FSINFO.
Fixed f_mkdir() on FAT32 creates incorrect directory.
R0.05a (February 03, 2008)
Added f_truncate() and f_utime().
Fixed off by one error at FAT sub-type determination.
Fixed btr in f_read() can be mistruncated.
Fixed cached sector is not flushed when create and close without write.
R0.06 (April 01, 2008)
Added fputc(), fputs(), fprintf() and fgets().
Improved performance of f_lseek() on moving to the same or following cluster.
R0.07 (April 01, 2009)
Merged Tiny-FatFs as a configuration option. (_FS_TINY)
Added long file name feature. (_USE_LFN)
Added multiple code page feature. (_CODE_PAGE)
Added re-entrancy for multitask operation. (_FS_REENTRANT)
Added auto cluster size selection to f_mkfs().
Added rewind option to f_readdir().
Changed result code of critical errors.
Renamed string functions to avoid name collision.
R0.07a (April 14, 2009)
Septemberarated out OS dependent code on reentrant cfg.
Added multiple sector size feature.
R0.07c (June 21, 2009)
Fixed f_unlink() can return FR_OK on error.
Fixed wrong cache control in f_lseek().
Added relative path feature.
Added f_chdir() and f_chdrive().
Added proper case conversion to extended character.
R0.07e (November 03, 2009)
Septemberarated out configuration options from ff.h to ffconf.h.
Fixed f_unlink() fails to remove a sub-directory on _FS_RPATH.
Fixed name matching error on the 13 character boundary.
Added a configuration option, _LFN_UNICODE.
Changed f_readdir() to return the SFN with always upper case on non-LFN cfg.
R0.08 (May 15, 2010)
Added a memory configuration option. (_USE_LFN = 3)
Added file lock feature. (_FS_SHARE)
Added fast seek feature. (_USE_FASTSEEK)
Changed some types on the API, XCHAR->TCHAR.
Changed .fname in the FILINFO structure on Unicode cfg.
String functions support UTF-8 encoding files on Unicode cfg.
R0.08a (August 16, 2010)
Added f_getcwd(). (_FS_RPATH = 2)
Added sector erase feature. (_USE_ERASE)
Moved file lock semaphore table from fs object to the bss.
Fixed f_mkfs() creates wrong FAT32 volume.
R0.08b (January 15, 2011)
Fast seek feature is also applied to f_read() and f_write().
f_lseek() reports required table size on creating CLMP.
Extended format syntax of f_printf().
Ignores duplicated directory separators in given path name.
R0.09 (September 06, 2011)
f_mkfs() supports multiple partition to complete the multiple partition feature.
Added f_fdisk().
R0.09a (August 27, 2012)
Changed f_open() and f_opendir() reject null object pointer to avoid crash.
Changed option name _FS_SHARE to _FS_LOCK.
Fixed assertion failure due to OS/2 EA on FAT12/16 volume.
R0.09b (January 24, 2013)
Added f_setlabel() and f_getlabel().
R0.10 (October 02, 2013)
Added selection of character encoding on the file. (_STRF_ENCODE)
Added f_closedir().
Added forced full FAT scan for f_getfree(). (_FS_NOFSINFO)
Added forced mount feature with changes of f_mount().
Improved behavior of volume auto detection.
Improved write throughput of f_puts() and f_printf().
Changed argument of f_chdrive(), f_mkfs(), disk_read() and disk_write().
Fixed f_write() can be truncated when the file size is close to 4GB.
Fixed f_open(), f_mkdir() and f_setlabel() can return incorrect value on error.
R0.10a (January 15, 2014)
Added arbitrary strings as drive number in the path name. (_STR_VOLUME_ID)
Added a configuration option of minimum sector size. (_MIN_SS)
2nd argument of f_rename() can have a drive number and it will be ignored.
Fixed f_mount() with forced mount fails when drive number is >= 1. (appeared at R0.10)
Fixed f_close() invalidates the file object without volume lock.
Fixed f_closedir() returns but the volume lock is left acquired. (appeared at R0.10)
Fixed creation of an entry with LFN fails on too many SFN collisions. (appeared at R0.07)
R0.10b (May 19, 2014)
Fixed a hard error in the disk I/O layer can collapse the directory entry.
Fixed LFN entry is not deleted when delete/rename an object with lossy converted SFN. (appeared at R0.07)
R0.10c (November 09, 2014)
Added a configuration option for the platforms without RTC. (_FS_NORTC)
Changed option name _USE_ERASE to _USE_TRIM.
Fixed volume label created by Mac OS X cannot be retrieved with f_getlabel(). (appeared at R0.09b)
Fixed a potential problem of FAT access that can appear on disk error.
Fixed null pointer dereference on attempting to delete the root direcotry. (appeared at R0.08)
R0.11 (February 09, 2015)
Added f_findfirst(), f_findnext() and f_findclose(). (_USE_FIND)
Fixed f_unlink() does not remove cluster chain of the file. (appeared at R0.10c)
Fixed _FS_NORTC option does not work properly. (appeared at R0.10c)
R0.11a (September 05, 2015)
Fixed wrong media change can lead a deadlock at thread-safe configuration.
Added code page 771, 860, 861, 863, 864, 865 and 869. (_CODE_PAGE)
Removed some code pages actually not exist on the standard systems. (_CODE_PAGE)
Fixed errors in the case conversion teble of code page 437 and 850 (ff.c).
Fixed errors in the case conversion teble of Unicode (cc*.c).
R0.12 (April 12, 2016)
Added support for exFAT file system. (_FS_EXFAT)
Added f_expand(). (_USE_EXPAND)
Changed some members in FINFO structure and behavior of f_readdir().
Added an option _USE_CHMOD.
Removed an option _WORD_ACCESS.
Fixed errors in the case conversion table of Unicode (cc*.c).
R0.12a (July 10, 2016)
Added support for creating exFAT volume with some changes of f_mkfs().
Added a file open method FA_OPEN_APPEND. An f_lseek() following f_open() is no longer needed.
f_forward() is available regardless of _FS_TINY.
Fixed f_mkfs() creates wrong volume. (appeared at R0.12)
Fixed wrong memory read in create_name(). (appeared at R0.12)
Fixed compilation fails at some configurations, _USE_FASTSEEK and _USE_FORWARD.
R0.12b (September 04, 2016)
Made f_rename() be able to rename objects with the same name but case.
Fixed an error in the case conversion teble of code page 866. (ff.c)
Fixed writing data is truncated at the file offset 4GiB on the exFAT volume. (appeared at R0.12)
Fixed creating a file in the root directory of exFAT volume can fail. (appeared at R0.12)
Fixed f_mkfs() creating exFAT volume with too small cluster size can collapse unallocated memory. (appeared at R0.12)
Fixed wrong object name can be returned when read directory at Unicode cfg. (appeared at R0.12)
Fixed large file allocation/removing on the exFAT volume collapses allocation bitmap. (appeared at R0.12)
Fixed some internal errors in f_expand() and f_lseek(). (appeared at R0.12)
R0.12c (March 04, 2017)
Improved write throughput at the fragmented file on the exFAT volume.
Made memory usage for exFAT be able to be reduced as decreasing _MAX_LFN.
Fixed successive f_getfree() can return wrong count on the FAT12/16 volume. (appeared at R0.12)
Fixed configuration option _VOLUMES cannot be set 10. (appeared at R0.10c)
R0.13 (May 21, 2017)
Changed heading character of configuration keywords "_" to "FF_".
Removed ASCII-only configuration, FF_CODE_PAGE = 1. Use FF_CODE_PAGE = 437 instead.
Added f_setcp(), run-time code page configuration. (FF_CODE_PAGE = 0)
Improved cluster allocation time on stretch a deep buried cluster chain.
Improved processing time of f_mkdir() with large cluster size by using FF_USE_LFN = 3.
Improved NoFatChain flag of the fragmented file to be set after it is truncated and got contiguous.
Fixed archive attribute is left not set when a file on the exFAT volume is renamed. (appeared at R0.12)
Fixed exFAT FAT entry can be collapsed when write or lseek operation to the existing file is done. (appeared at R0.12c)
Fixed creating a file can fail when a new cluster allocation to the exFAT directory occures. (appeared at R0.12c)
R0.13a (October 14, 2017)
Added support for UTF-8 encoding on the API. (FF_LFN_UNICODE = 2)
Added options for file name output buffer. (FF_LFN_BUF, FF_SFN_BUF).
Added dynamic memory allocation option for working buffer of f_mkfs() and f_fdisk().
Fixed f_fdisk() and f_mkfs() create the partition table with wrong CHS parameters. (appeared at R0.09)
Fixed f_unlink() can cause lost clusters at fragmented file on the exFAT volume. (appeared at R0.12c)
Fixed f_setlabel() rejects some valid characters for exFAT volume. (appeared at R0.12)
R0.13b (April 07, 2018)
Added support for UTF-32 encoding on the API. (FF_LFN_UNICODE = 3)
Added support for Unix style volume ID. (FF_STR_VOLUME_ID = 2)
Fixed accesing any object on the exFAT root directory beyond the cluster boundary can fail. (appeared at R0.12c)
Fixed f_setlabel() does not reject some invalid characters. (appeared at R0.09b)
R0.13c (October 14, 2018)
Supported stdint.h for C99 and later. (integer.h was included in ff.h)
Fixed reading a directory gets infinite loop when the last directory entry is not empty. (appeared at R0.12)
Fixed creating a sub-directory in the fragmented sub-directory on the exFAT volume collapses FAT chain of the parent directory. (appeared at R0.12)
Fixed f_getcwd() cause output buffer overrun when the buffer has a valid drive number. (appeared at R0.13b)
R0.14 (October 14, 2019)
Added support for 64-bit LBA and GUID partition table (FF_LBA64 = 1)
Changed some API functions, f_mkfs() and f_fdisk().
Fixed f_open() function cannot find the file with file name in length of FF_MAX_LFN characters.
Fixed f_readdir() function cannot retrieve long file names in length of FF_MAX_LFN - 1 characters.
Fixed f_readdir() function returns file names with wrong case conversion. (appeared at R0.12)
Fixed f_mkfs() function can fail to create exFAT volume in the second partition. (appeared at R0.12)
R0.14a (December 5, 2020)
Limited number of recursive calls in f_findnext().
Fixed old floppy disks formatted with MS-DOS 2.x and 3.x cannot be mounted.
Fixed some compiler warnings.
R0.14b (April 17, 2021)
Made FatFs uses standard library <string.h> for copy, compare and search instead of built-in string functions.
Added support for long long integer and floating point to f_printf(). (FF_STRF_LLI and FF_STRF_FP)
Made path name parser ignore the terminating separator to allow "dir/".
Improved the compatibility in Unix style path name feature.
Fixed the file gets dead-locked when f_open() failed with some conditions. (appeared at R0.12a)
Fixed f_mkfs() can create wrong exFAT volume due to a timing dependent error. (appeared at R0.12)
Fixed code page 855 cannot be set by f_setcp().
Fixed some compiler warnings.
R0.15 (November 6, 2022)
Changed user provided synchronization functions in order to completely eliminate the platform dependency from FatFs code.
FF_SYNC_t is removed from the configuration options.
Fixed a potential error in f_mount when FF_FS_REENTRANT.
Fixed file lock control FF_FS_LOCK is not mutal excluded when FF_FS_REENTRANT && FF_VOLUMES > 1 is true.
Fixed f_mkfs() creates broken exFAT volume when the size of volume is >= 2^32 sectors.
Fixed string functions cannot write the unicode characters not in BMP when FF_LFN_UNICODE == 2 (UTF-8).
Fixed a compatibility issue in identification of GPT header.
FatFs Module Source Files R0.15
FILES
00readme.txt This file.
00history.txt Revision history.
ff.c FatFs module.
ffconf.h Configuration file of FatFs module.
ff.h Common include file for FatFs and application module.
diskio.h Common include file for FatFs and disk I/O module.
diskio.c An example of glue function to attach existing disk I/O module to FatFs.
ffunicode.c Optional Unicode utility functions.
ffsystem.c An example of optional O/S related functions.
Low level disk I/O module is not included in this archive because the FatFs
module is only a generic file system layer and it does not depend on any specific
storage device. You need to provide a low level disk I/O module written to
control the storage device that attached to the target system.
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module SKELETON for FatFs (C)ChaN, 2019 */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various exsisting */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*/
#include "ff.h" /* Obtains integer types */
#include "diskio.h" /* Declarations of disk functions */
/* Definitions of physical drive number for each drive */
#define DEV_RAM 0 /* Example: Map Ramdisk to physical drive 0 */
#define DEV_MMC 1 /* Example: Map MMC/SD card to physical drive 1 */
#define DEV_USB 2 /* Example: Map USB MSD to physical drive 2 */
/*-----------------------------------------------------------------------*/
/* Get Drive Status */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat;
int result;
switch (pdrv) {
case DEV_RAM :
result = RAM_disk_status();
// translate the reslut code here
return stat;
case DEV_MMC :
result = MMC_disk_status();
// translate the reslut code here
return stat;
case DEV_USB :
result = USB_disk_status();
// translate the reslut code here
return stat;
}
return STA_NOINIT;
}
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat;
int result;
switch (pdrv) {
case DEV_RAM :
result = RAM_disk_initialize();
// translate the reslut code here
return stat;
case DEV_MMC :
result = MMC_disk_initialize();
// translate the reslut code here
return stat;
case DEV_USB :
result = USB_disk_initialize();
// translate the reslut code here
return stat;
}
return STA_NOINIT;
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s) */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
BYTE *buff, /* Data buffer to store read data */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to read */
)
{
DRESULT res;
int result;
switch (pdrv) {
case DEV_RAM :
// translate the arguments here
result = RAM_disk_read(buff, sector, count);
// translate the reslut code here
return res;
case DEV_MMC :
// translate the arguments here
result = MMC_disk_read(buff, sector, count);
// translate the reslut code here
return res;
case DEV_USB :
// translate the arguments here
result = USB_disk_read(buff, sector, count);
// translate the reslut code here
return res;
}
return RES_PARERR;
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s) */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
DRESULT disk_write (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
const BYTE *buff, /* Data to be written */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
)
{
DRESULT res;
int result;
switch (pdrv) {
case DEV_RAM :
// translate the arguments here
result = RAM_disk_write(buff, sector, count);
// translate the reslut code here
return res;
case DEV_MMC :
// translate the arguments here
result = MMC_disk_write(buff, sector, count);
// translate the reslut code here
return res;
case DEV_USB :
// translate the arguments here
result = USB_disk_write(buff, sector, count);
// translate the reslut code here
return res;
}
return RES_PARERR;
}
#endif
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions */
/*-----------------------------------------------------------------------*/
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
DRESULT res;
int result;
switch (pdrv) {
case DEV_RAM :
// Process of the command for the RAM drive
return res;
case DEV_MMC :
// Process of the command for the MMC/SD card
return res;
case DEV_USB :
// Process of the command the USB drive
return res;
}
return RES_PARERR;
}
/*-----------------------------------------------------------------------/
/ Low level disk interface modlue include file (C)ChaN, 2019 /
/-----------------------------------------------------------------------*/
#ifndef _DISKIO_DEFINED
#define _DISKIO_DEFINED
#include "ff.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Status of Disk Functions */
typedef BYTE DSTATUS;
/* Results of Disk Functions */
typedef enum {
RES_OK = 0, /* 0: Successful */
RES_ERROR, /* 1: R/W Error */
RES_WRPRT, /* 2: Write Protected */
RES_NOTRDY, /* 3: Not Ready */
RES_PARERR /* 4: Invalid Parameter */
} DRESULT;
/*---------------------------------------*/
/* Prototypes for disk control functions */
DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
/* Disk Status Bits (DSTATUS) */
#define STA_NOINIT 0x01 /* Drive not initialized */
#define STA_NODISK 0x02 /* No medium in the drive */
#define STA_PROTECT 0x04 /* Write protected */
/* Command code for disk_ioctrl fucntion */
/* Generic command (Used by FatFs) */
#define CTRL_SYNC 0 /* Complete pending write process (needed at FF_FS_READONLY == 0) */
#define GET_SECTOR_COUNT 1 /* Get media size (needed at FF_USE_MKFS == 1) */
#define GET_SECTOR_SIZE 2 /* Get sector size (needed at FF_MAX_SS != FF_MIN_SS) */
#define GET_BLOCK_SIZE 3 /* Get erase block size (needed at FF_USE_MKFS == 1) */
#define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at FF_USE_TRIM == 1) */
/* Generic command (Not used by FatFs) */
#define CTRL_POWER 5 /* Get/Set power status */
#define CTRL_LOCK 6 /* Lock/Unlock media removal */
#define CTRL_EJECT 7 /* Eject media */
#define CTRL_FORMAT 8 /* Create physical format on the media */
/* MMC/SDC specific ioctl command */
#define MMC_GET_TYPE 10 /* Get card type */
#define MMC_GET_CSD 11 /* Get CSD */
#define MMC_GET_CID 12 /* Get CID */
#define MMC_GET_OCR 13 /* Get OCR */
#define MMC_GET_SDSTAT 14 /* Get SD status */
#define ISDIO_READ 55 /* Read data form SD iSDIO register */
#define ISDIO_WRITE 56 /* Write data to SD iSDIO register */
#define ISDIO_MRITE 57 /* Masked write data to SD iSDIO register */
/* ATA/CF specific ioctl command */
#define ATA_GET_REV 20 /* Get F/W revision */
#define ATA_GET_MODEL 21 /* Get model name */
#define ATA_GET_SN 22 /* Get serial number */
#ifdef __cplusplus
}
#endif
#endif
This source diff could not be displayed because it is too large. You can view the blob instead.
/*----------------------------------------------------------------------------/
/ FatFs - Generic FAT Filesystem module R0.15 /
/-----------------------------------------------------------------------------/
/
/ Copyright (C) 2022, ChaN, all right reserved.
/
/ FatFs module is an open source software. Redistribution and use of FatFs in
/ source and binary forms, with or without modification, are permitted provided
/ that the following condition is met:
/ 1. Redistributions of source code must retain the above copyright notice,
/ this condition and the following disclaimer.
/
/ This software is provided by the copyright holder and contributors "AS IS"
/ and any warranties related to this software are DISCLAIMED.
/ The copyright owner or contributors be NOT LIABLE for any damages caused
/ by use of this software.
/
/----------------------------------------------------------------------------*/
#ifndef FF_DEFINED
#define FF_DEFINED 80286 /* Revision ID */
#ifdef __cplusplus
extern "C" {
#endif
#include "ffconf.h" /* FatFs configuration options */
#if FF_DEFINED != FFCONF_DEF
#error Wrong configuration file (ffconf.h).
#endif
/* Integer types used for FatFs API */
#if defined(_WIN32) /* Windows VC++ (for development only) */
#define FF_INTDEF 2
#include <windows.h>
typedef unsigned __int64 QWORD;
#include <float.h>
#define isnan(v) _isnan(v)
#define isinf(v) (!_finite(v))
#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */
#define FF_INTDEF 2
#include <stdint.h>
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
typedef unsigned char BYTE; /* char must be 8-bit */
typedef uint16_t WORD; /* 16-bit unsigned integer */
typedef uint32_t DWORD; /* 32-bit unsigned integer */
typedef uint64_t QWORD; /* 64-bit unsigned integer */
typedef WORD WCHAR; /* UTF-16 character type */
#else /* Earlier than C99 */
#define FF_INTDEF 1
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
typedef unsigned char BYTE; /* char must be 8-bit */
typedef unsigned short WORD; /* 16-bit unsigned integer */
typedef unsigned long DWORD; /* 32-bit unsigned integer */
typedef WORD WCHAR; /* UTF-16 character type */
#endif
/* Type of file size and LBA variables */
#if FF_FS_EXFAT
#if FF_INTDEF != 2
#error exFAT feature wants C99 or later
#endif
typedef QWORD FSIZE_t;
#if FF_LBA64
typedef QWORD LBA_t;
#else
typedef DWORD LBA_t;
#endif
#else
#if FF_LBA64
#error exFAT needs to be enabled when enable 64-bit LBA
#endif
typedef DWORD FSIZE_t;
typedef DWORD LBA_t;
#endif
/* Type of path name strings on FatFs API (TCHAR) */
#if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */
typedef WCHAR TCHAR;
#define _T(x) L ## x
#define _TEXT(x) L ## x
#elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */
typedef char TCHAR;
#define _T(x) u8 ## x
#define _TEXT(x) u8 ## x
#elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */
typedef DWORD TCHAR;
#define _T(x) U ## x
#define _TEXT(x) U ## x
#elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3)
#error Wrong FF_LFN_UNICODE setting
#else /* ANSI/OEM code in SBCS/DBCS */
typedef char TCHAR;
#define _T(x) x
#define _TEXT(x) x
#endif
/* Definitions of volume management */
#if FF_MULTI_PARTITION /* Multiple partition configuration */
typedef struct {
BYTE pd; /* Physical drive number */
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
} PARTITION;
extern PARTITION VolToPart[]; /* Volume - Partition mapping table */
#endif
#if FF_STR_VOLUME_ID
#ifndef FF_VOLUME_STRS
extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */
#endif
#endif
/* Filesystem object structure (FATFS) */
typedef struct {
BYTE fs_type; /* Filesystem type (0:not mounted) */
BYTE pdrv; /* Volume hosting physical drive */
BYTE ldrv; /* Logical drive number (used only when FF_FS_REENTRANT) */
BYTE n_fats; /* Number of FATs (1 or 2) */
BYTE wflag; /* win[] status (b0:dirty) */
BYTE fsi_flag; /* FSINFO status (b7:disabled, b0:dirty) */
WORD id; /* Volume mount ID */
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
WORD csize; /* Cluster size [sectors] */
#if FF_MAX_SS != FF_MIN_SS
WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */
#endif
#if FF_USE_LFN
WCHAR* lfnbuf; /* LFN working buffer */
#endif
#if FF_FS_EXFAT
BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */
#endif
#if !FF_FS_READONLY
DWORD last_clst; /* Last allocated cluster */
DWORD free_clst; /* Number of free clusters */
#endif
#if FF_FS_RPATH
DWORD cdir; /* Current directory start cluster (0:root) */
#if FF_FS_EXFAT
DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */
DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */
DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */
#endif
#endif
DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */
DWORD fsize; /* Number of sectors per FAT */
LBA_t volbase; /* Volume base sector */
LBA_t fatbase; /* FAT base sector */
LBA_t dirbase; /* Root directory base sector (FAT12/16) or cluster (FAT32/exFAT) */
LBA_t database; /* Data base sector */
#if FF_FS_EXFAT
LBA_t bitbase; /* Allocation bitmap base sector */
#endif
LBA_t winsect; /* Current sector appearing in the win[] */
BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */
} FATFS;
/* Object ID and allocation information (FFOBJID) */
typedef struct {
FATFS* fs; /* Pointer to the hosting volume of this object */
WORD id; /* Hosting volume's mount ID */
BYTE attr; /* Object attribute */
BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */
DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */
FSIZE_t objsize; /* Object size (valid when sclust != 0) */
#if FF_FS_EXFAT
DWORD n_cont; /* Size of first fragment - 1 (valid when stat == 3) */
DWORD n_frag; /* Size of last fragment needs to be written to FAT (valid when not zero) */
DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */
DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */
DWORD c_ofs; /* Offset in the containing directory (valid when file object and sclust != 0) */
#endif
#if FF_FS_LOCK
UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */
#endif
} FFOBJID;
/* File object structure (FIL) */
typedef struct {
FFOBJID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */
BYTE flag; /* File status flags */
BYTE err; /* Abort flag (error code) */
FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */
DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */
LBA_t sect; /* Sector number appearing in buf[] (0:invalid) */
#if !FF_FS_READONLY
LBA_t dir_sect; /* Sector number containing the directory entry (not used at exFAT) */
BYTE* dir_ptr; /* Pointer to the directory entry in the win[] (not used at exFAT) */
#endif
#if FF_USE_FASTSEEK
DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */
#endif
#if !FF_FS_TINY
BYTE buf[FF_MAX_SS]; /* File private data read/write window */
#endif
} FIL;
/* Directory object structure (DIR) */
typedef struct {
FFOBJID obj; /* Object identifier */
DWORD dptr; /* Current read/write offset */
DWORD clust; /* Current cluster */
LBA_t sect; /* Current sector (0:Read operation has terminated) */
BYTE* dir; /* Pointer to the directory item in the win[] */
BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */
#if FF_USE_LFN
DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
#endif
#if FF_USE_FIND
const TCHAR* pat; /* Pointer to the name matching pattern */
#endif
} DIR;
/* File information structure (FILINFO) */
typedef struct {
FSIZE_t fsize; /* File size */
WORD fdate; /* Modified date */
WORD ftime; /* Modified time */
BYTE fattrib; /* File attribute */
#if FF_USE_LFN
TCHAR altname[FF_SFN_BUF + 1];/* Alternative file name */
TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */
#else
TCHAR fname[12 + 1]; /* File name */
#endif
} FILINFO;
/* Format parameter structure (MKFS_PARM) */
typedef struct {
BYTE fmt; /* Format option (FM_FAT, FM_FAT32, FM_EXFAT and FM_SFD) */
BYTE n_fat; /* Number of FATs */
UINT align; /* Data area alignment (sector) */
UINT n_root; /* Number of root directory entries */
DWORD au_size; /* Cluster size (byte) */
} MKFS_PARM;
/* File function return code (FRESULT) */
typedef enum {
FR_OK = 0, /* (0) Succeeded */
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
FR_INT_ERR, /* (2) Assertion failed */
FR_NOT_READY, /* (3) The physical drive cannot work */
FR_NO_FILE, /* (4) Could not find the file */
FR_NO_PATH, /* (5) Could not find the path */
FR_INVALID_NAME, /* (6) The path name format is invalid */
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
FR_EXIST, /* (8) Access denied due to prohibited access */
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
FR_NOT_ENABLED, /* (12) The volume has no work area */
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
} FRESULT;
/*--------------------------------------------------------------*/
/* FatFs Module Application Interface */
/*--------------------------------------------------------------*/
FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
FRESULT f_close (FIL* fp); /* Close an open file object */
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */
FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */
FRESULT f_truncate (FIL* fp); /* Truncate the file */
FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */
FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */
FRESULT f_closedir (DIR* dp); /* Close an open directory */
FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */
FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */
FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */
FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */
FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */
FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */
FRESULT f_chdir (const TCHAR* path); /* Change current directory */
FRESULT f_chdrive (const TCHAR* path); /* Change current drive */
FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */
FRESULT f_setlabel (const TCHAR* label); /* Set volume label */
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */
FRESULT f_expand (FIL* fp, FSIZE_t fsz, BYTE opt); /* Allocate a contiguous block to the file */
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */
FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len); /* Create a FAT volume */
FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work); /* Divide a physical drive into some partitions */
FRESULT f_setcp (WORD cp); /* Set current code page */
int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */
int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */
int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */
TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */
/* Some API fucntions are implemented as macro */
#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize))
#define f_error(fp) ((fp)->err)
#define f_tell(fp) ((fp)->fptr)
#define f_size(fp) ((fp)->obj.objsize)
#define f_rewind(fp) f_lseek((fp), 0)
#define f_rewinddir(dp) f_readdir((dp), 0)
#define f_rmdir(path) f_unlink(path)
#define f_unmount(path) f_mount(0, path, 0)
/*--------------------------------------------------------------*/
/* Additional Functions */
/*--------------------------------------------------------------*/
/* RTC function (provided by user) */
#if !FF_FS_READONLY && !FF_FS_NORTC
DWORD get_fattime (void); /* Get current time */
#endif
/* LFN support functions (defined in ffunicode.c) */
#if FF_USE_LFN >= 1
WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */
WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */
DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */
#endif
/* O/S dependent functions (samples available in ffsystem.c) */
#if FF_USE_LFN == 3 /* Dynamic memory allocation */
void* ff_memalloc (UINT msize); /* Allocate memory block */
void ff_memfree (void* mblock); /* Free memory block */
#endif
#if FF_FS_REENTRANT /* Sync functions */
int ff_mutex_create (int vol); /* Create a sync object */
void ff_mutex_delete (int vol); /* Delete a sync object */
int ff_mutex_take (int vol); /* Lock sync object */
void ff_mutex_give (int vol); /* Unlock sync object */
#endif
/*--------------------------------------------------------------*/
/* Flags and Offset Address */
/*--------------------------------------------------------------*/
/* File access mode and open method flags (3rd argument of f_open) */
#define FA_READ 0x01
#define FA_WRITE 0x02
#define FA_OPEN_EXISTING 0x00
#define FA_CREATE_NEW 0x04
#define FA_CREATE_ALWAYS 0x08
#define FA_OPEN_ALWAYS 0x10
#define FA_OPEN_APPEND 0x30
/* Fast seek controls (2nd argument of f_lseek) */
#define CREATE_LINKMAP ((FSIZE_t)0 - 1)
/* Format options (2nd argument of f_mkfs) */
#define FM_FAT 0x01
#define FM_FAT32 0x02
#define FM_EXFAT 0x04
#define FM_ANY 0x07
#define FM_SFD 0x08
/* Filesystem type (FATFS.fs_type) */
#define FS_FAT12 1
#define FS_FAT16 2
#define FS_FAT32 3
#define FS_EXFAT 4
/* File attribute bits for directory entry (FILINFO.fattrib) */
#define AM_RDO 0x01 /* Read only */
#define AM_HID 0x02 /* Hidden */
#define AM_SYS 0x04 /* System */
#define AM_DIR 0x10 /* Directory */
#define AM_ARC 0x20 /* Archive */
#ifdef __cplusplus
}
#endif
#endif /* FF_DEFINED */
/*------------------------------------------------------------------------*/
/* A Sample Code of User Provided OS Dependent Functions for FatFs */
/*------------------------------------------------------------------------*/
#include "ff.h"
#if FF_USE_LFN == 3 /* Use dynamic memory allocation */
/*------------------------------------------------------------------------*/
/* Allocate/Free a Memory Block */
/*------------------------------------------------------------------------*/
#include <stdlib.h> /* with POSIX API */
void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */
UINT msize /* Number of bytes to allocate */
)
{
return malloc((size_t)msize); /* Allocate a new memory block */
}
void ff_memfree (
void* mblock /* Pointer to the memory block to free (no effect if null) */
)
{
free(mblock); /* Free the memory block */
}
#endif
#if FF_FS_REENTRANT /* Mutal exclusion */
/*------------------------------------------------------------------------*/
/* Definitions of Mutex */
/*------------------------------------------------------------------------*/
#define OS_TYPE 0 /* 0:Win32, 1:uITRON4.0, 2:uC/OS-II, 3:FreeRTOS, 4:CMSIS-RTOS */
#if OS_TYPE == 0 /* Win32 */
#include <windows.h>
static HANDLE Mutex[FF_VOLUMES + 1]; /* Table of mutex handle */
#elif OS_TYPE == 1 /* uITRON */
#include "itron.h"
#include "kernel.h"
static mtxid Mutex[FF_VOLUMES + 1]; /* Table of mutex ID */
#elif OS_TYPE == 2 /* uc/OS-II */
#include "includes.h"
static OS_EVENT *Mutex[FF_VOLUMES + 1]; /* Table of mutex pinter */
#elif OS_TYPE == 3 /* FreeRTOS */
#include "FreeRTOS.h"
#include "semphr.h"
static SemaphoreHandle_t Mutex[FF_VOLUMES + 1]; /* Table of mutex handle */
#elif OS_TYPE == 4 /* CMSIS-RTOS */
#include "cmsis_os.h"
static osMutexId Mutex[FF_VOLUMES + 1]; /* Table of mutex ID */
#endif
/*------------------------------------------------------------------------*/
/* Create a Mutex */
/*------------------------------------------------------------------------*/
/* This function is called in f_mount function to create a new mutex
/ or semaphore for the volume. When a 0 is returned, the f_mount function
/ fails with FR_INT_ERR.
*/
int ff_mutex_create ( /* Returns 1:Function succeeded or 0:Could not create the mutex */
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
)
{
#if OS_TYPE == 0 /* Win32 */
Mutex[vol] = CreateMutex(NULL, FALSE, NULL);
return (int)(Mutex[vol] != INVALID_HANDLE_VALUE);
#elif OS_TYPE == 1 /* uITRON */
T_CMTX cmtx = {TA_TPRI,1};
Mutex[vol] = acre_mtx(&cmtx);
return (int)(Mutex[vol] > 0);
#elif OS_TYPE == 2 /* uC/OS-II */
OS_ERR err;
Mutex[vol] = OSMutexCreate(0, &err);
return (int)(err == OS_NO_ERR);
#elif OS_TYPE == 3 /* FreeRTOS */
Mutex[vol] = xSemaphoreCreateMutex();
return (int)(Mutex[vol] != NULL);
#elif OS_TYPE == 4 /* CMSIS-RTOS */
osMutexDef(cmsis_os_mutex);
Mutex[vol] = osMutexCreate(osMutex(cmsis_os_mutex));
return (int)(Mutex[vol] != NULL);
#endif
}
/*------------------------------------------------------------------------*/
/* Delete a Mutex */
/*------------------------------------------------------------------------*/
/* This function is called in f_mount function to delete a mutex or
/ semaphore of the volume created with ff_mutex_create function.
*/
void ff_mutex_delete ( /* Returns 1:Function succeeded or 0:Could not delete due to an error */
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
)
{
#if OS_TYPE == 0 /* Win32 */
CloseHandle(Mutex[vol]);
#elif OS_TYPE == 1 /* uITRON */
del_mtx(Mutex[vol]);
#elif OS_TYPE == 2 /* uC/OS-II */
OS_ERR err;
OSMutexDel(Mutex[vol], OS_DEL_ALWAYS, &err);
#elif OS_TYPE == 3 /* FreeRTOS */
vSemaphoreDelete(Mutex[vol]);
#elif OS_TYPE == 4 /* CMSIS-RTOS */
osMutexDelete(Mutex[vol]);
#endif
}
/*------------------------------------------------------------------------*/
/* Request a Grant to Access the Volume */
/*------------------------------------------------------------------------*/
/* This function is called on enter file functions to lock the volume.
/ When a 0 is returned, the file function fails with FR_TIMEOUT.
*/
int ff_mutex_take ( /* Returns 1:Succeeded or 0:Timeout */
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
)
{
#if OS_TYPE == 0 /* Win32 */
return (int)(WaitForSingleObject(Mutex[vol], FF_FS_TIMEOUT) == WAIT_OBJECT_0);
#elif OS_TYPE == 1 /* uITRON */
return (int)(tloc_mtx(Mutex[vol], FF_FS_TIMEOUT) == E_OK);
#elif OS_TYPE == 2 /* uC/OS-II */
OS_ERR err;
OSMutexPend(Mutex[vol], FF_FS_TIMEOUT, &err));
return (int)(err == OS_NO_ERR);
#elif OS_TYPE == 3 /* FreeRTOS */
return (int)(xSemaphoreTake(Mutex[vol], FF_FS_TIMEOUT) == pdTRUE);
#elif OS_TYPE == 4 /* CMSIS-RTOS */
return (int)(osMutexWait(Mutex[vol], FF_FS_TIMEOUT) == osOK);
#endif
}
/*------------------------------------------------------------------------*/
/* Release a Grant to Access the Volume */
/*------------------------------------------------------------------------*/
/* This function is called on leave file functions to unlock the volume.
*/
void ff_mutex_give (
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
)
{
#if OS_TYPE == 0 /* Win32 */
ReleaseMutex(Mutex[vol]);
#elif OS_TYPE == 1 /* uITRON */
unl_mtx(Mutex[vol]);
#elif OS_TYPE == 2 /* uC/OS-II */
OSMutexPost(Mutex[vol]);
#elif OS_TYPE == 3 /* FreeRTOS */
xSemaphoreGive(Mutex[vol]);
#elif OS_TYPE == 4 /* CMSIS-RTOS */
osMutexRelease(Mutex[vol]);
#endif
}
#endif /* FF_FS_REENTRANT */
This source diff could not be displayed because it is too large. You can view the blob instead.
/* crash.h
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
// Original from M0AGX (blog@m0agx.eu), "Preserving debugging breadcrumbs across
// reboots in Cortex-M,"
// https://m0agx.eu/2018/08/18/preserving-debugging-breadcrumbs-across-reboots-in-cortex-m/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <time.h>
//
#include "pico/stdlib.h"
#ifdef __cplusplus
extern "C" {
#endif
/* The crash info section is at the beginning of the RAM,
* that is not initialized by the linker to preserve
* information across reboots.
*/
/**
* These are the positions of the fault frame elements in the
* fault frame structure.
*/
enum {
R0_Pos = 0, /**< The position of the R0 content in a fault structure */
R1_Pos, /**< The position of the R1 content in a fault structure */
R2_Pos, /**< The position of the R2 content in a fault structure */
R3_Pos, /**< The position of the R3 content in a fault structure */
R12_Pos, /**< The position of the R12 content in a fault structure */
LR_Pos, /**< The position of the LR content in a fault structure */
PC_Pos, /**< The position of the PC content in a fault structure */
PSR_Pos, /**< The position of the PSR content in a fault structure */
NUM_REGS, /**< The number of registers in the fault frame */
};
typedef struct {
uint32_t r0; /**< R0 register content */
uint32_t r1; /**< R1 register content */
uint32_t r2; /**< R2 register content */
uint32_t r3; /**< R3 register content */
uint32_t r12; /**< R12 register content */
uint32_t lr; /**< LR register content */
uint32_t pc; /**< PC register content */
uint32_t psr; /**< PSR register content */
} __attribute__((packed)) cy_stc_fault_frame_t;
typedef enum {
crash_magic_none = 0,
crash_magic_bootloader_entry = 0xB000B000,
crash_magic_hard_fault = 0xCAFEBABE,
crash_magic_debug_mon = 0x01020304,
crash_magic_reboot_requested = 0x00ABCDEF,
crash_magic_stack_overflow = 0x0BADBEEF,
crash_magic_assert = 0xDEBDEBDE
} crash_magic_t;
typedef struct {
char file[32];
int line;
char func[32];
char pred[32];
} crash_assert_t;
typedef struct {
uint32_t magic;
time_t timestamp;
union {
cy_stc_fault_frame_t cy_faultFrame;
crash_assert_t assert;
char calling_func[64];
};
uint8_t xor_checksum; // last to avoid including in calculation
} crash_info_t;
// Trick to find struct size at compile time:
// char (*__kaboom)[sizeof(crash_info_flash_t)] = 1;
// warning: initialization of 'char (*)[132]' from 'int' makes ...
void crash_handler_init();
const crash_info_t *crash_handler_get_info();
volatile const crash_info_t *crash_handler_get_info_flash();
#define SYSTEM_RESET() system_reset_func(__FUNCTION__)
void system_reset_func(char const *const func) __attribute__((noreturn));
void capture_assert(const char *file, int line, const char *func, const char *pred)
__attribute__((noreturn));
void capture_assert_case_not(const char *file, int line, const char *func, int v)
__attribute__((noreturn));
int dump_crash_info(crash_info_t const *const pCrashInfo, int next, char *const buf,
size_t const buf_sz);
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */
/* crc.h
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
/* Derived from:
* SD/MMC File System Library
* Copyright (c) 2016 Neil Thiessen
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SD_CRC_H
#define SD_CRC_H
#include <stddef.h>
#include <stdint.h>
/**
* @brief Calculate the CRC7 checksum for the specified data block.
*
* This function calculates the CRC7 checksum for the specified data block
* using the lookup table defined in the m_Crc7Table array.
*
* @param data The data block to be checked.
* @param length The length of the data block in bytes.
* @return The calculated checksum.
*/
__attribute__((optimize("Ofast")))
static inline char crc7(uint8_t const *data, int const length) {
extern const char m_Crc7Table[];
char crc = 0;
for (int i = 0; i < length; i++) {
crc = m_Crc7Table[(crc << 1) ^ data[i]];
}
//Return the calculated checksum
return crc;
}
/**
* @brief Calculate the CRC16 checksum for the specified data block.
*
* This function calculates the CRC16 checksum for the specified data block
* using the lookup table defined in the m_Crc7Table array.
*
* @param data The data block to be checked.
* @param length The length of the data block in bytes.
* @return The calculated checksum.
*/
uint16_t crc16(uint8_t const *data, int const length);
#endif
/* [] END OF FILE */
/*
* delays.h
*
* Created on: Apr 25, 2022
* Author: carlk
*/
/* Using millis() or micros() for timeouts
For example,
uint32_t start = millis();
do {
// ...
}
} while (millis() - start < TIMEOUT);
There is no problem if the millis() counter wraps,
due to the properties of unsigned integer modulo arithmetic.
"A computation involving unsigned operands can never overflow,
because a result that cannot be represented by the resulting
unsigned integer type is reduced modulo the number that is
one greater than the largest value that can be represented
by the resulting type."
-- ISO/IEC 9899:1999 (E) §6.2.5/9
In other words, a uint32_t will wrap at 0 and UINT_MAX.
So, for example,
0x00000000 - 0xFFFFFFFF = 0x00000001
Remember that an unsigned integer will never be negative!
Be careful with comparisons. In the example above,
if 0x00000000 is the result of the counter wrapping,
and 0xFFFFFFFF is the start timestamp, a comparison like
millis() - start < TIMEOUT
is OK, but the following code is problematic if, say, the first call
to millis() returns 0xFFFFFFF0 and the second
call to millis() returns 0xFFFFFFFF:
uint32_t end = millis() + 100; // end = 0x00000054
while (millis() < end) // while (0xFFFFFFFF < 0x00000054)
*/
#pragma once
#include <stdint.h>
//
#include "pico/stdlib.h"
#if PICO_RP2040
#include "RP2040.h"
#else
#include "RP2350.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
static inline uint32_t millis() {
__COMPILER_BARRIER();
return time_us_64() / 1000;
__COMPILER_BARRIER();
}
static inline void delay_ms(uint32_t ulTime_ms) {
sleep_ms(ulTime_ms);
}
static inline uint64_t micros() {
__COMPILER_BARRIER();
return to_us_since_boot(get_absolute_time());
__COMPILER_BARRIER();
}
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */
/* f_util.h
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#pragma once
#include "ff.h"
#ifdef __cplusplus
extern "C" {
#endif
const char *FRESULT_str(FRESULT i);
FRESULT delete_node (
TCHAR* path, /* Path name buffer with the sub-directory to delete */
UINT sz_buff, /* Size of path name buffer (items) */
FILINFO* fno /* Name read buffer */
);
void ls(const char *dir);
#ifdef __cplusplus
}
#endif
/* ff_stdio.h
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
// For compatibility with FreeRTOS+FAT API
#include <errno.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
//
#include "ff.h"
//
#include "my_debug.h"
#define BaseType_t int
#define FF_FILE FIL
#define ff_rewind f_rewind
#define pvPortMalloc malloc
#define vPortFree free
#define ffconfigMAX_FILENAME 250
#define configASSERT myASSERT
#define FF_PRINTF printf
#define pdFREERTOS_ERRNO_NONE 0
#define FF_EOF (-1)
#define FF_SEEK_SET 0
#define FF_SEEK_CUR 1
#define FF_SEEK_END 2
#define pdFALSE 0
#define pdTRUE 1
#define ff_filelength f_size
#define ff_feof f_eof
typedef struct FF_STAT {
uint32_t st_size; /* Size of the object in number of bytes. */
// uint16_t st_mode; /* The mode (attribute bits) of this
// file or directory. */
} FF_Stat_t;
typedef struct {
DIR dir;
FILINFO fileinfo;
const char *pcFileName;
uint32_t ulFileSize;
//uint8_t ucAttributes;
} FF_FindData_t;
FF_FILE *ff_fopen(const char *pcFile, const char *pcMode);
int ff_fclose(FF_FILE *pxStream);
int ff_stat(const char *pcFileName, FF_Stat_t *pxStatBuffer);
size_t ff_fwrite(const void *pvBuffer, size_t xSize, size_t xItems,
FF_FILE *pxStream);
size_t ff_fread(void *pvBuffer, size_t xSize, size_t xItems, FF_FILE *pxStream);
int ff_chdir(const char *pcDirectoryName);
char *ff_getcwd(char *pcBuffer, size_t xBufferLength);
int ff_mkdir(const char *pcPath);
int ff_fputc(int iChar, FF_FILE *pxStream);
int ff_fgetc(FF_FILE *pxStream);
int ff_rmdir(const char *pcDirectory);
int ff_remove(const char *pcPath);
long ff_ftell(FF_FILE *pxStream);
int ff_fseek(FF_FILE *pxStream, int iOffset, int iWhence);
int ff_findfirst(const char *pcDirectory, FF_FindData_t *pxFindData);
int ff_findnext( FF_FindData_t *pxFindData );
FF_FILE *ff_truncate( const char * pcFileName, long lTruncateSize );
int ff_seteof( FF_FILE *pxStream );
int ff_rename( const char *pcOldName, const char *pcNewName, int bDeleteIfExists );
char *ff_fgets(char *pcBuffer, size_t xCount, FF_FILE *pxStream);
/*---------------------------------------------------------------------------/
/ Configurations of FatFs Module
/---------------------------------------------------------------------------*/
#define FFCONF_DEF 80286 /* Revision ID */
/*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_READONLY 0
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
/ and optional writing functions as well. */
#define FF_FS_MINIMIZE 0
/* This option defines minimization level to remove some basic API functions.
/
/ 0: Basic functions are fully enabled.
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
/ are removed.
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
/ 3: f_lseek() function is removed in addition to 2. */
#define FF_USE_FIND 1
/* This option switches filtered directory read functions, f_findfirst() and
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
#define FF_USE_MKFS 1
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
#define FF_USE_FASTSEEK 1
/* This option switches fast seek function. (0:Disable or 1:Enable) */
#define FF_USE_EXPAND 1
/* This option switches f_expand function. (0:Disable or 1:Enable) */
#define FF_USE_CHMOD 0
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
#define FF_USE_LABEL 0
/* This option switches volume label functions, f_getlabel() and f_setlabel().
/ (0:Disable or 1:Enable) */
#define FF_USE_FORWARD 0
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
#define FF_USE_STRFUNC 1
#define FF_PRINT_LLI 1
#define FF_PRINT_FLOAT 1
#define FF_STRF_ENCODE 3
/* FF_USE_STRFUNC switches string functions, f_gets(), f_putc(), f_puts() and
/ f_printf().
/
/ 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
/ 1: Enable without LF-CRLF conversion.
/ 2: Enable with LF-CRLF conversion.
/
/ FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
/ makes f_printf() support floating point argument. These features want C99 or later.
/ When FF_LFN_UNICODE >= 1 with LFN enabled, string functions convert the character
/ encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
/ to be read/written via those functions.
/
/ 0: ANSI/OEM in current CP
/ 1: Unicode in UTF-16LE
/ 2: Unicode in UTF-16BE
/ 3: Unicode in UTF-8
*/
/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/
#define FF_CODE_PAGE 437
/* This option specifies the OEM code page to be used on the target system.
/ Incorrect code page setting can cause a file open failure.
/
/ 437 - U.S.
/ 720 - Arabic
/ 737 - Greek
/ 771 - KBL
/ 775 - Baltic
/ 850 - Latin 1
/ 852 - Latin 2
/ 855 - Cyrillic
/ 857 - Turkish
/ 860 - Portuguese
/ 861 - Icelandic
/ 862 - Hebrew
/ 863 - Canadian French
/ 864 - Arabic
/ 865 - Nordic
/ 866 - Russian
/ 869 - Greek 2
/ 932 - Japanese (DBCS)
/ 936 - Simplified Chinese (DBCS)
/ 949 - Korean (DBCS)
/ 950 - Traditional Chinese (DBCS)
/ 0 - Include all code pages above and configured by f_setcp()
*/
#define FF_USE_LFN 3
#define FF_MAX_LFN 255
/* The FF_USE_LFN switches the support for LFN (long file name).
/
/ 0: Disable LFN. FF_MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
/
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
/ be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN
/ specification.
/ When use stack for the working buffer, take care on stack overflow. When use heap
/ memory for the working buffer, memory management functions, ff_memalloc() and
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
#define FF_LFN_UNICODE 2
/* This option switches the character encoding on the API when LFN is enabled.
/
/ 0: ANSI/OEM in current CP (TCHAR = char)
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
/ 2: Unicode in UTF-8 (TCHAR = char)
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
/
/ Also behavior of string I/O functions will be affected by this option.
/ When LFN is not enabled, this option has no effect. */
#define FF_LFN_BUF 255
#define FF_SFN_BUF 12
/* This set of options defines size of file name members in the FILINFO structure
/ which is used to read out directory items. These values should be suffcient for
/ the file names to read. The maximum possible length of the read file name depends
/ on character encoding. When LFN is not enabled, these options have no effect. */
#define FF_FS_RPATH 2
/* This option configures support for relative path.
/
/ 0: Disable relative path and remove related functions.
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
/ 2: f_getcwd() function is available in addition to 1.
*/
/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/
# define FF_VOLUMES 4
/* Number of volumes (logical drives) to be used. (1-10) */
#define FF_STR_VOLUME_ID 0
#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
/ logical drives. Number of items must not be less than FF_VOLUMES. Valid
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
/ not defined, a user defined volume string table is needed as:
/
/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
*/
#define FF_MULTI_PARTITION 0
/* This option switches support for multiple volumes on the physical drive.
/ By default (0), each logical drive number is bound to the same physical drive
/ number and only an FAT volume found on the physical drive will be mounted.
/ When this function is enabled (1), each logical drive number can be bound to
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
/ function will be available. */
#define FF_MIN_SS 512
#define FF_MAX_SS 512
/* This set of options configures the range of sector size to be supported. (512,
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
/ harddisk, but a larger value may be required for on-board flash memory and some
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
/ for variable sector size mode and disk_ioctl() function needs to implement
/ GET_SECTOR_SIZE command. */
#define FF_LBA64 1
/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */
#define FF_MIN_GPT 0x10000000
/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs and
/ f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */
#define FF_USE_TRIM 0
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
/ disk_ioctl() function. */
/*---------------------------------------------------------------------------/
/ System Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_TINY 0
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
/ Instead of private sector buffer eliminated from the file object, common sector
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
#define FF_FS_EXFAT 1
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
#define FF_FS_NORTC 0
#define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2022
/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
/ an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
/ timestamp feature. Every object modified by FatFs will have a fixed timestamp
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
#define FF_FS_NOFSINFO 0
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/ option, and f_getfree() function at the first time after volume mount will force
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/ bit0=0: Use free cluster count in the FSINFO if available.
/ bit0=1: Do not trust free cluster count in the FSINFO.
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
*/
#define FF_FS_LOCK 16
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
/ is 1.
/
/ 0: Disable file lock function. To avoid volume corruption, application program
/ should avoid illegal open, remove and rename to the open objects.
/ >0: Enable file lock function. The value defines how many files/sub-directories
/ can be opened simultaneously under file lock control. Note that the file
/ lock control is independent of re-entrancy. */
#define FF_FS_REENTRANT 0
#define FF_FS_TIMEOUT 1000
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/ module itself. Note that regardless of this option, file access to different
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
/ to the same volume is under control of this featuer.
/
/ 0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
/ ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give()
/ function, must be added to the project. Samples are available in ffsystem.c.
/
/ The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
*/
/*--- End of configuration options ---*/
/*
* file_stream.h
*
* Wraps a FreeRTOS+FAT FF_FILE in a standard I/O stream FILE
* to take advantage of the buffering provided by the standard I/O library
* for a tremendous speedup of random-sized writes (e.g., fprintf output).
*
* Created on: Jun 20, 2024
* Author: carlk
*/
#pragma once
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
FILE *open_file_stream( const char *pcFile, const char *pcMode );
#ifdef __cplusplus
}
#endif
/* hw_config.h
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#pragma once
#include <stddef.h>
#include "sd_card.h"
#ifdef __cplusplus
extern "C" {
#endif
/* FatFS supports up to 10 logical drives. By default, each logical
drive is associated with the physical drive in same drive number. */
/* Return the number of physical drives (SD card sockets) in the configuration */
size_t sd_get_num();
/* Return a pointer to the SD card "object" at the given physical drive number.
(See http://elm-chan.org/fsw/ff/doc/filename.html#vol.)
Parameter `num` must be less than sd_get_num(). */
sd_card_t* sd_get_by_num(size_t num);
/* See http://elm-chan.org/fsw/ff/doc/config.html#str_volume_id */
#if FF_STR_VOLUME_ID
extern const char* VolumeStr[FF_VOLUMES]; /* User defined volume ID */
#endif
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */
/* my_debug.h
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
/* USE_PRINTF
If this is defined and not zero,
these message output functions will use the Pico SDK's stdout.
*/
/* USE_DBG_PRINTF
If this is not defined or is zero or NDEBUG is defined,
DBG_PRINTF statements will be effectively stripped from the code.
*/
/* Single string output callbacks: send message output somewhere.
To use these, do not define the USE_PRINTF compile definition,
and override these "weak" functions by strongly implementing them in user code.
The weak implementations do nothing.
*/
void put_out_error_message(const char *s);
void put_out_info_message(const char *s);
void put_out_debug_message(const char *s);
// https://gcc.gnu.org/onlinedocs/gcc-3.2.3/cpp/Variadic-Macros.html
int error_message_printf(const char *func, int line, const char *fmt, ...)
__attribute__((format(__printf__, 3, 4)));
#ifndef EMSG_PRINTF
#define EMSG_PRINTF(fmt, ...) error_message_printf(__func__, __LINE__, fmt, ##__VA_ARGS__)
#endif
int error_message_printf_plain(const char *fmt, ...) __attribute__((format(__printf__, 1, 2)));
int debug_message_printf(const char *func, int line, const char *fmt, ...)
__attribute__((format(__printf__, 3, 4)));
#ifndef DBG_PRINTF
# if defined(USE_DBG_PRINTF) && USE_DBG_PRINTF // && !defined(NDEBUG)
# define DBG_PRINTF(fmt, ...) debug_message_printf(__func__, __LINE__, fmt, ##__VA_ARGS__)
# else
# define DBG_PRINTF(fmt, ...) (void)0
# endif
#endif
int info_message_printf(const char *fmt, ...) __attribute__((format(__printf__, 1, 2)));
#ifndef IMSG_PRINTF
#define IMSG_PRINTF(fmt, ...) info_message_printf(fmt, ##__VA_ARGS__)
#endif
void lock_printf();
void unlock_printf();
void my_assert_func(const char *file, int line, const char *func, const char *pred)
__attribute__((noreturn));
#ifdef NDEBUG /* required by ANSI standard */
# define myASSERT(__e) ((void)0)
#else
# define myASSERT(__e) \
{ ((__e) ? (void)0 : my_assert_func(__func__, __LINE__, __func__, #__e)); }
#endif
void assert_always_func(const char *file, int line, const char *func, const char *pred)
__attribute__((noreturn));
#define ASSERT_ALWAYS(__e) \
((__e) ? (void)0 : my_assert_func(__FILE__, __LINE__, __func__, #__e))
void assert_case_is(const char *file, int line, const char *func, int v, int expected)
__attribute__((noreturn));
#define ASSERT_CASE_IS(__v, __e) \
((__v == __e) ? (void)0 : assert_case_is(__FILE__, __LINE__, __func__, __v, __e))
void assert_case_not_func(const char *file, int line, const char *func, int v)
__attribute__((noreturn));
#define ASSERT_CASE_NOT(__v) (assert_case_not_func(__FILE__, __LINE__, __func__, __v))
#ifdef NDEBUG /* required by ANSI standard */
#define DBG_ASSERT_CASE_NOT(__e) ((void)0)
#else
#define DBG_ASSERT_CASE_NOT(__v) (assert_case_not_func(__FILE__, __LINE__, __func__, __v))
#endif
static inline void dump_bytes(size_t num, uint8_t bytes[]) {
(void)num;
(void)bytes;
DBG_PRINTF(" ");
for (size_t j = 0; j < 16; ++j) {
DBG_PRINTF("%02hhx", j);
if (j < 15)
DBG_PRINTF(" ");
else {
DBG_PRINTF("\n");
}
}
for (size_t i = 0; i < num; i += 16) {
DBG_PRINTF("%04x ", i);
for (size_t j = 0; j < 16 && i + j < num; ++j) {
DBG_PRINTF("%02hhx", bytes[i + j]);
if (j < 15)
DBG_PRINTF(" ");
else {
DBG_PRINTF("\n");
}
}
}
DBG_PRINTF("\n");
}
void dump8buf(char *buf, size_t buf_sz, uint8_t *pbytes, size_t nbytes);
void hexdump_8(const char *s, const uint8_t *pbytes, size_t nbytes);
bool compare_buffers_8(const char *s0, const uint8_t *pbytes0, const char *s1,
const uint8_t *pbytes1, const size_t nbytes);
// sz is size in BYTES!
void hexdump_32(const char *s, const uint32_t *pwords, size_t nwords);
// sz is size in BYTES!
bool compare_buffers_32(const char *s0, const uint32_t *pwords0, const char *s1,
const uint32_t *pwords1, const size_t nwords);
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */
/* my_rtc.h
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <time.h>
extern time_t epochtime;
void time_init();
#ifdef __cplusplus
}
#endif
/* rtc.h
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <time.h>
extern time_t epochtime;
void time_init();
#ifdef __cplusplus
}
#endif
#pragma once
#include <stdint.h>
typedef struct {
uint32_t sd_command;
unsigned sd_command_retries;
unsigned sd_lock;
unsigned sd_spi_read;
unsigned sd_spi_write;
unsigned sd_spi_write_read;
unsigned spi_lock;
unsigned rp2040_sdio_command_R1;
unsigned rp2040_sdio_command_R2;
unsigned rp2040_sdio_command_R3;
unsigned rp2040_sdio_rx_poll;
unsigned rp2040_sdio_tx_poll;
unsigned sd_sdio_begin;
unsigned sd_sdio_stopTransmission;
} sd_timeouts_t;
extern sd_timeouts_t sd_timeouts;
/* util.h
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <string.h>
//
#if PICO_RP2040
#include "RP2040.h"
#else
#include "RP2350.h"
#endif
//
#include "my_debug.h"
#ifdef __cplusplus
extern "C" {
#endif
// Greatest Common Divisor: Euclidian Algorithm
// https://www.freecodecamp.org/news/euclidian-gcd-algorithm-greatest-common-divisor/
int gcd(int a,int b);
typedef int (*printer_t)(const char* format, ...);
// works with negative index
static inline int wrap_ix(int index, int n)
{
return ((index % n) + n) % n;
}
// Calculate arr indices with wrap around (+ and -)
static inline int mod_floor(int a, int n) {
return ((a % n) + n) % n;
}
__attribute__((always_inline)) static inline uint32_t calculate_checksum(uint32_t const *p, size_t const size){
uint32_t checksum = 0;
for (uint32_t i = 0; i < (size/sizeof(uint32_t))-1; i++){
checksum ^= *p;
p++;
}
return checksum;
}
static inline void ext_str(size_t const data_sz,
uint8_t const data[],
size_t const msb,
size_t const lsb,
size_t const buf_sz,
char buf[]) {
memset(buf, 0, buf_sz);
size_t size = (1 + msb - lsb) / 8; // bytes
size_t byte = (data_sz - 1) - (msb / 8);
for (uint32_t i = 0; i < size; i++) {
myASSERT(i < buf_sz);
myASSERT(byte < data_sz);
buf[i] = data[byte++];
}
}
static inline uint32_t ext_bits(size_t n_src_bytes, unsigned char const *data, int msb, int lsb) {
uint32_t bits = 0;
uint32_t size = 1 + msb - lsb;
for (uint32_t i = 0; i < size; i++) {
uint32_t position = lsb + i;
uint32_t byte = (n_src_bytes - 1) - (position >> 3);
uint32_t bit = position & 0x7;
uint32_t value = (data[byte] >> bit) & 1;
bits |= value << i;
}
return bits;
}
static inline uint32_t ext_bits16(unsigned char const *data, int msb, int lsb) {
return ext_bits(16, data, msb, lsb);
}
char const* uint8_binary_str(uint8_t number);
char const* uint_binary_str(unsigned int number);
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */
/**
* Copyright (c) 2011-2022 Bill Greiman
* This file is part of the SdFat library for SD memory cards.
*
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#ifndef sd_sdio_h
#define sd_sdio_h
#include "sd_card.h"
/** Initialize the SD card.
* \return true for success or false for failure.
*/
bool sd_sdio_begin(sd_card_t *sd_card_p);
/** CMD6 Switch mode: Check Function Set Function.
* \param[in] arg CMD6 argument.
* \param[out] status return status data.
*
* \return true for success or false for failure.
*/
bool sd_sdio_cardCMD6(sd_card_t *sd_card_p, uint32_t arg, uint8_t *status);
/** Disable an SDIO card.
* not implemented.
*/
// void sd_sdio_end() {}
#ifndef DOXYGEN_SHOULD_SKIP_THIS
uint32_t __attribute__((error("use sectorCount()"))) cardSize();
#endif // DOXYGEN_SHOULD_SKIP_THIS
/** Erase a range of sectors.
*
* \param[in] firstSector The address of the first sector in the range.
* \param[in] lastSector The address of the last sector in the range.
*
* \note This function requests the SD card to do a flash erase for a
* range of sectors. The data on the card after an erase operation is
* either 0 or 1, depends on the card vendor. The card must support
* single sector erase.
*
* \return true for success or false for failure.
*/
bool sd_sdio_erase(sd_card_t *sd_card_p, uint32_t firstSector, uint32_t lastSector);
/**
* \return code for the last error. See SdCardInfo.h for a list of error codes.
*/
uint8_t sd_sdio_errorCode() /* const */;
/** \return error data for last error. */
uint32_t sd_sdio_errorData() /* const */;
/** \return error line for last error. Tmp function for debug. */
uint32_t sd_sdio_errorLine() /* const */;
/**
* Check for busy with CMD13.
*
* \return true if busy else false.
*/
bool sd_sdio_isBusy();
/** \return the SD clock frequency in kHz. */
//uint32_t sd_sdio_kHzSdClk();
/**
* Read a 512 byte sector from an SD card.
*
* \param[in] sector Logical sector to be read.
* \param[out] dst Pointer to the location that will receive the data.
* \return true for success or false for failure.
*/
bool sd_sdio_readSector(sd_card_t *sd_card_p, uint32_t sector, uint8_t *dst);
/**
* Read multiple 512 byte sectors from an SD card.
*
* \param[in] sector Logical sector to be read.
* \param[in] ns Number of sectors to be read.
* \param[out] dst Pointer to the location that will receive the data.
* \return true for success or false for failure.
*/
bool sd_sdio_readSectors(sd_card_t *sd_card_p, uint32_t sector, uint8_t *dst, size_t ns);
// Read 512-bit SD status
bool rp2040_sdio_get_sd_status(sd_card_t *sd_card_p, uint8_t response[64]);
/** Read one data sector in a multiple sector read sequence
*
* \param[out] dst Pointer to the location for the data to be read.
*
* \return true for success or false for failure.
*/
bool sd_sdio_readData(sd_card_t *sd_card_p, uint8_t *dst);
/** Read OCR register.
*
* \param[out] ocr Value of OCR register.
* \return true for success or false for failure.
*/
bool sd_sdio_readOCR(sd_card_t *sd_card_p, uint32_t *ocr);
/** Read SCR register.
*
* \param[out] scr Value of SCR register.
* \return true for success or false for failure.
*/
// bool sd_sdio_readSCR(sd_card_t *sd_card_p, scr_t *scr);
/** Start a read multiple sectors sequence.
*
* \param[in] sector Address of first sector in sequence.
*
* \note This function is used with readData() and readStop() for optimized
* multiple sector reads. SPI chipSelect must be low for the entire sequence.
*
* \return true for success or false for failure.
*/
// bool sd_sdio_readStart(uint32_t sector);
/** Start a read multiple sectors sequence.
*
* \param[in] sector Address of first sector in sequence.
* \param[in] count Maximum sector count.
* \note This function is used with readData() and readStop() for optimized
* multiple sector reads. SPI chipSelect must be low for the entire sequence.
*
* \return true for success or false for failure.
*/
bool sd_sdio_readStart(sd_card_t *sd_card_p, uint32_t sector, uint32_t count);
/** End a read multiple sectors sequence.
*
* \return true for success or false for failure.
*/
bool sd_sdio_readStop(sd_card_t *sd_card_p);
/** \return SDIO card status. */
uint32_t sd_sdio_status(sd_card_t *sd_card_p);
/**
* Determine the size of an SD flash memory card.
*
* \return The number of 512 byte data sectors in the card
* or zero if an error occurs.
*/
uint32_t sd_sdio_sectorCount(sd_card_t *sd_card_p);
/**
* Send CMD12 to stop read or write.
*
* \param[in] blocking If true, wait for command complete.
*
* \return true for success or false for failure.
*/
bool sd_sdio_stopTransmission(sd_card_t *sd_card_p, bool blocking);
/** \return success if sync successful. Not for user apps. */
//bool sd_sdio_syncDevice(sd_card_t *sd_card_p);
/** Return the card type: SD V1, SD V2 or SDHC
* \return 0 - SD V1, 1 - SD V2, or 3 - SDHC.
*/
uint8_t sd_sdio_type(sd_card_t *sd_card_p) /* const */;
/**
* Writes a 512 byte sector to an SD card.
*
* \param[in] sector Logical sector to be written.
* \param[in] src Pointer to the location of the data to be written.
* \return true for success or false for failure.
*/
bool sd_sdio_writeSector(sd_card_t *sd_card_p, uint32_t sector, const uint8_t *src);
/**
* Write multiple 512 byte sectors to an SD card.
*
* \param[in] sector Logical sector to be written.
* \param[in] ns Number of sectors to be written.
* \param[in] src Pointer to the location of the data to be written.
* \return true for success or false for failure.
*/
bool sd_sdio_writeSectors(sd_card_t *sd_card_p, uint32_t sector, const uint8_t *src, size_t ns);
/** Write one data sector in a multiple sector write sequence.
* \param[in] src Pointer to the location of the data to be written.
* \return true for success or false for failure.
*/
bool sd_sdio_writeData(sd_card_t *sd_card_p, const uint8_t *src);
/** Start a write multiple sectors sequence.
*
* \param[in] sector Address of first sector in sequence.
*
* \note This function is used with writeData() and writeStop()
* for optimized multiple sector writes.
*
* \return true for success or false for failure.
*/
// bool sd_sdio_writeStart(sd_card_t *sd_card_p, uint32_t sector);
/** Start a write multiple sectors sequence.
*
* \param[in] sector Address of first sector in sequence.
* \param[in] count Maximum sector count.
* \note This function is used with writeData() and writeStop()
* for optimized multiple sector writes.
*
* \return true for success or false for failure.
*/
bool sd_sdio_writeStart(sd_card_t *sd_card_p, uint32_t sector, uint32_t count);
/** End a write multiple sectors sequence.
*
* \return true for success or false for failure.
*/
bool sd_sdio_writeStop(sd_card_t *sd_card_p);
void sd_sdio_ctor(sd_card_t *sd_card_p);
#endif // sd_sdio_h
// Platform-specific definitions for ZuluSCSI RP2040 hardware.
#pragma once
#include <stdint.h>
// #include <Arduino.h>
// #include "ZuluSCSI_platform_gpio.h"
// #include "scsiHostPhy.h"
#include "SdioCard.h"
#include "pico/stdlib.h"
#ifdef __cplusplus
extern "C" {
#endif
#define delayMicroseconds sleep_us
/* These are used in debug output and default SCSI strings */
extern const char *g_azplatform_name;
#define PLATFORM_NAME "ZuluSCSI RP2040"
#define PLATFORM_REVISION "2.0"
#define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_SYNC_10
#define PLATFORM_OPTIMAL_MIN_SD_WRITE_SIZE 32768
#define PLATFORM_OPTIMAL_MAX_SD_WRITE_SIZE 65536
#define PLATFORM_OPTIMAL_LAST_SD_WRITE_SIZE 8192
#define SD_USE_SDIO 1
#define PLATFORM_HAS_INITIATOR_MODE 1
// NOTE: The driver supports synchronous speeds higher than 10MB/s, but this
// has not been tested due to lack of fast enough SCSI adapter.
// #define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_TURBO
// Debug logging function, can be used to print to e.g. serial port.
// May get called from interrupt handlers.
void azplatform_log(const char *s);
void azplatform_emergency_log_save();
#if 0
// Timing and delay functions.
// Arduino platform already provides these
// unsigned long millis(void);
void delay(unsigned long ms);
// Short delays, can be called from interrupt mode
static inline void delay_ns(unsigned long ns)
{
delayMicroseconds((ns + 999) / 1000);
}
#endif
// Approximate fast delay
static inline void delay_100ns()
{
asm volatile ("nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop");
}
// Initialize SD card and GPIO configuration
void azplatform_init();
// Initialization for main application, not used for bootloader
void azplatform_late_init();
// Disable the status LED
void azplatform_disable_led(void);
// Query whether initiator mode is enabled on targets with PLATFORM_HAS_INITIATOR_MODE
bool azplatform_is_initiator_mode_enabled();
// Setup soft watchdog if supported
void azplatform_reset_watchdog();
// Set callback that will be called during data transfer to/from SD card.
// This can be used to implement simultaneous transfer to SCSI bus.
typedef void (*sd_callback_t)(uint32_t bytes_complete);
void azplatform_set_sd_callback(sd_callback_t func, const uint8_t *buffer);
// Reprogram firmware in main program area.
#ifndef RP2040_DISABLE_BOOTLOADER
#define AZPLATFORM_BOOTLOADER_SIZE (128 * 1024)
#define AZPLATFORM_FLASH_TOTAL_SIZE (1024 * 1024)
#define AZPLATFORM_FLASH_PAGE_SIZE 4096
bool azplatform_rewrite_flash_page(uint32_t offset, uint8_t buffer[AZPLATFORM_FLASH_PAGE_SIZE]);
void azplatform_boot_to_main_firmware();
#endif
// ROM drive in the unused external flash area
#ifndef RP2040_DISABLE_ROMDRIVE
#define PLATFORM_HAS_ROM_DRIVE 1
// Check maximum available space for ROM drive in bytes
uint32_t azplatform_get_romdrive_maxsize();
// Read ROM drive area
bool azplatform_read_romdrive(uint8_t *dest, uint32_t start, uint32_t count);
// Reprogram ROM drive area
#define AZPLATFORM_ROMDRIVE_PAGE_SIZE 4096
bool azplatform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t count);
#endif
// Parity lookup tables for write and read from SCSI bus.
// These are used by macros below and the code in scsi_accel_rp2040.cpp
extern const uint16_t g_scsi_parity_lookup[256];
extern const uint16_t g_scsi_parity_check_lookup[512];
// Below are GPIO access definitions that are used from scsiPhy.cpp.
// Write a single SCSI pin.
// Example use: SCSI_OUT(ATN, 1) sets SCSI_ATN to low (active) state.
#define SCSI_OUT(pin, state) \
*(state ? &sio_hw->gpio_clr : &sio_hw->gpio_set) = 1 << (SCSI_OUT_ ## pin)
// Read a single SCSI pin.
// Example use: SCSI_IN(ATN), returns 1 for active low state.
#define SCSI_IN(pin) \
((sio_hw->gpio_in & (1 << (SCSI_IN_ ## pin))) ? 0 : 1)
// Set pin directions for initiator vs. target mode
#define SCSI_ENABLE_INITIATOR() \
(sio_hw->gpio_oe_set = (1 << SCSI_OUT_ACK) | \
(1 << SCSI_OUT_ATN)), \
(sio_hw->gpio_oe_clr = (1 << SCSI_IN_IO) | \
(1 << SCSI_IN_CD) | \
(1 << SCSI_IN_MSG) | \
(1 << SCSI_IN_REQ))
// Enable driving of shared control pins
#define SCSI_ENABLE_CONTROL_OUT() \
(sio_hw->gpio_oe_set = (1 << SCSI_OUT_CD) | \
(1 << SCSI_OUT_MSG))
// Set SCSI data bus to output
#define SCSI_ENABLE_DATA_OUT() \
(sio_hw->gpio_clr = (1 << SCSI_DATA_DIR), \
sio_hw->gpio_oe_set = SCSI_IO_DATA_MASK)
// Write SCSI data bus, also sets REQ to inactive.
#define SCSI_OUT_DATA(data) \
gpio_put_masked(SCSI_IO_DATA_MASK | (1 << SCSI_OUT_REQ), \
g_scsi_parity_lookup[(uint8_t)(data)] | (1 << SCSI_OUT_REQ)), \
SCSI_ENABLE_DATA_OUT()
// Release SCSI data bus and REQ signal
#define SCSI_RELEASE_DATA_REQ() \
(sio_hw->gpio_oe_clr = SCSI_IO_DATA_MASK, \
sio_hw->gpio_set = (1 << SCSI_DATA_DIR) | (1 << SCSI_OUT_REQ))
// Release all SCSI outputs
#define SCSI_RELEASE_OUTPUTS() \
SCSI_RELEASE_DATA_REQ(), \
sio_hw->gpio_oe_clr = (1 << SCSI_OUT_CD) | \
(1 << SCSI_OUT_MSG), \
sio_hw->gpio_set = (1 << SCSI_OUT_IO) | \
(1 << SCSI_OUT_CD) | \
(1 << SCSI_OUT_MSG) | \
(1 << SCSI_OUT_RST) | \
(1 << SCSI_OUT_BSY) | \
(1 << SCSI_OUT_REQ) | \
(1 << SCSI_OUT_SEL)
// Read SCSI data bus
#define SCSI_IN_DATA() \
(~sio_hw->gpio_in & SCSI_IO_DATA_MASK) >> SCSI_IO_SHIFT
// SD card driver for SdFat
#ifdef SD_USE_SDIO
struct SdioConfig;
// extern SdioConfig g_sd_sdio_config;
// #define SD_CONFIG g_sd_sdio_config
#define SD_CONFIG_CRASH g_sd_sdio_config
#else
struct SdSpiConfig;
extern SdSpiConfig g_sd_spi_config;
#define SD_CONFIG g_sd_spi_config
#define SD_CONFIG_CRASH g_sd_spi_config
#endif
#ifdef __cplusplus
}
#endif
// Implementation of SDIO communication for RP2040
//
// The RP2040 official work-in-progress code at
// https://github.com/raspberrypi/pico-extras/tree/master/src/rp2_common/pico_sd_card
// may be useful reference, but this is independent implementation.
//
// For official SDIO specifications, refer to:
// https://www.sdcard.org/downloads/pls/
// "SDIO Physical Layer Simplified Specification Version 8.00"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
//
#include "hardware/dma.h"
#include "hardware/gpio.h"
#include "hardware/pio.h"
#if PICO_RP2040
#include "RP2040.h"
#else
#include "RP2350.h"
#endif
//
#include "dma_interrupts.h"
#include "hw_config.h"
#include "rp2040_sdio.h"
#include "rp2040_sdio.pio.h"
#include "delays.h"
#include "sd_card.h"
#include "sd_timeouts.h"
#include "my_debug.h"
#include "util.h"
//
#include "rp2040_sdio.h"
#define azdbg(arg1, ...) {\
DBG_PRINTF("%s,%s:%d %s\n", __func__, __FILE__, __LINE__, arg1); \
}
#define STATE sd_card_p->sdio_if_p->state
#define SDIO_PIO sd_card_p->sdio_if_p->SDIO_PIO
#define SDIO_CMD_SM STATE.SDIO_CMD_SM
#define SDIO_DATA_SM STATE.SDIO_DATA_SM
#define SDIO_DMA_CH STATE.SDIO_DMA_CH
#define SDIO_DMA_CHB STATE.SDIO_DMA_CHB
#define SDIO_CMD sd_card_p->sdio_if_p->CMD_gpio
#define SDIO_CLK sd_card_p->sdio_if_p->CLK_gpio
#define SDIO_D0 sd_card_p->sdio_if_p->D0_gpio
#define SDIO_D1 sd_card_p->sdio_if_p->D1_gpio
#define SDIO_D2 sd_card_p->sdio_if_p->D2_gpio
#define SDIO_D3 sd_card_p->sdio_if_p->D3_gpio
// Force everything to idle state
static sdio_status_t rp2040_sdio_stop();
/*******************************************************
* Checksum algorithms
*******************************************************/
// Table lookup for calculating CRC-7 checksum that is used in SDIO command packets.
// Usage:
// uint8_t crc = 0;
// crc = crc7_table[crc ^ byte];
// .. repeat for every byte ..
static const uint8_t crc7_table[256] = {
0x00, 0x12, 0x24, 0x36, 0x48, 0x5a, 0x6c, 0x7e, 0x90, 0x82, 0xb4, 0xa6, 0xd8, 0xca, 0xfc, 0xee,
0x32, 0x20, 0x16, 0x04, 0x7a, 0x68, 0x5e, 0x4c, 0xa2, 0xb0, 0x86, 0x94, 0xea, 0xf8, 0xce, 0xdc,
0x64, 0x76, 0x40, 0x52, 0x2c, 0x3e, 0x08, 0x1a, 0xf4, 0xe6, 0xd0, 0xc2, 0xbc, 0xae, 0x98, 0x8a,
0x56, 0x44, 0x72, 0x60, 0x1e, 0x0c, 0x3a, 0x28, 0xc6, 0xd4, 0xe2, 0xf0, 0x8e, 0x9c, 0xaa, 0xb8,
0xc8, 0xda, 0xec, 0xfe, 0x80, 0x92, 0xa4, 0xb6, 0x58, 0x4a, 0x7c, 0x6e, 0x10, 0x02, 0x34, 0x26,
0xfa, 0xe8, 0xde, 0xcc, 0xb2, 0xa0, 0x96, 0x84, 0x6a, 0x78, 0x4e, 0x5c, 0x22, 0x30, 0x06, 0x14,
0xac, 0xbe, 0x88, 0x9a, 0xe4, 0xf6, 0xc0, 0xd2, 0x3c, 0x2e, 0x18, 0x0a, 0x74, 0x66, 0x50, 0x42,
0x9e, 0x8c, 0xba, 0xa8, 0xd6, 0xc4, 0xf2, 0xe0, 0x0e, 0x1c, 0x2a, 0x38, 0x46, 0x54, 0x62, 0x70,
0x82, 0x90, 0xa6, 0xb4, 0xca, 0xd8, 0xee, 0xfc, 0x12, 0x00, 0x36, 0x24, 0x5a, 0x48, 0x7e, 0x6c,
0xb0, 0xa2, 0x94, 0x86, 0xf8, 0xea, 0xdc, 0xce, 0x20, 0x32, 0x04, 0x16, 0x68, 0x7a, 0x4c, 0x5e,
0xe6, 0xf4, 0xc2, 0xd0, 0xae, 0xbc, 0x8a, 0x98, 0x76, 0x64, 0x52, 0x40, 0x3e, 0x2c, 0x1a, 0x08,
0xd4, 0xc6, 0xf0, 0xe2, 0x9c, 0x8e, 0xb8, 0xaa, 0x44, 0x56, 0x60, 0x72, 0x0c, 0x1e, 0x28, 0x3a,
0x4a, 0x58, 0x6e, 0x7c, 0x02, 0x10, 0x26, 0x34, 0xda, 0xc8, 0xfe, 0xec, 0x92, 0x80, 0xb6, 0xa4,
0x78, 0x6a, 0x5c, 0x4e, 0x30, 0x22, 0x14, 0x06, 0xe8, 0xfa, 0xcc, 0xde, 0xa0, 0xb2, 0x84, 0x96,
0x2e, 0x3c, 0x0a, 0x18, 0x66, 0x74, 0x42, 0x50, 0xbe, 0xac, 0x9a, 0x88, 0xf6, 0xe4, 0xd2, 0xc0,
0x1c, 0x0e, 0x38, 0x2a, 0x54, 0x46, 0x70, 0x62, 0x8c, 0x9e, 0xa8, 0xba, 0xc4, 0xd6, 0xe0, 0xf2
};
// Calculate the CRC16 checksum for parallel 4 bit lines separately.
// When the SDIO bus operates in 4-bit mode, the CRC16 algorithm
// is applied to each line separately and generates total of
// 4 x 16 = 64 bits of checksum.
__attribute__((optimize("Ofast")))
uint64_t sdio_crc16_4bit_checksum(uint32_t *data, uint32_t num_words)
{
uint64_t crc = 0;
uint32_t *end = data + num_words;
while (data < end)
{
for (int unroll = 0; unroll < 4; unroll++)
{
// Each 32-bit word contains 8 bits per line.
// Reverse the bytes because SDIO protocol is big-endian.
uint32_t data_in = __builtin_bswap32(*data++);
// Shift out 8 bits for each line
uint32_t data_out = crc >> 32;
crc <<= 32;
// XOR outgoing data to itself with 4 bit delay
data_out ^= (data_out >> 16);
// XOR incoming data to outgoing data with 4 bit delay
data_out ^= (data_in >> 16);
// XOR outgoing and incoming data to accumulator at each tap
uint64_t xorred = data_out ^ data_in;
crc ^= xorred;
crc ^= xorred << (5 * 4);
crc ^= xorred << (12 * 4);
}
}
return crc;
}
/*******************************************************
* Basic SDIO command execution
*******************************************************/
static void sdio_send_command(const sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint8_t response_bits)
{
// azdbg("SDIO Command: ", (int)command, " arg ", arg);
// Format the arguments in the way expected by the PIO code.
uint32_t word0 =
(47 << 24) | // Number of bits in command minus one
( 1 << 22) | // Transfer direction from host to card
(command << 16) | // Command byte
(((arg >> 24) & 0xFF) << 8) | // MSB byte of argument
(((arg >> 16) & 0xFF) << 0);
uint32_t word1 =
(((arg >> 8) & 0xFF) << 24) |
(((arg >> 0) & 0xFF) << 16) | // LSB byte of argument
( 1 << 8); // End bit
// Set number of bits in response minus one, or leave at 0 if no response expected
if (response_bits)
{
word1 |= ((response_bits - 1) << 0);
}
// Calculate checksum in the order that the bytes will be transmitted (big-endian)
uint8_t crc = 0;
crc = crc7_table[crc ^ ((word0 >> 16) & 0xFF)];
crc = crc7_table[crc ^ ((word0 >> 8) & 0xFF)];
crc = crc7_table[crc ^ ((word0 >> 0) & 0xFF)];
crc = crc7_table[crc ^ ((word1 >> 24) & 0xFF)];
crc = crc7_table[crc ^ ((word1 >> 16) & 0xFF)];
word1 |= crc << 8;
// Transmit command
pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM);
pio_sm_put(SDIO_PIO, SDIO_CMD_SM, word0);
pio_sm_put(SDIO_PIO, SDIO_CMD_SM, word1);
}
sdio_status_t rp2040_sdio_command_R1(sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint32_t *response)
{
sdio_send_command(sd_card_p, command, arg, response ? 48 : 0);
// Wait for response
uint32_t start = millis();
uint32_t wait_words = response ? 2 : 1;
while (pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM) < wait_words)
{
if ((uint32_t)(millis() - start) > sd_timeouts.rp2040_sdio_command_R1)
{
if (command != 8) // Don't log for missing SD card
{
azdbg("Timeout waiting for response in rp2040_sdio_command_R1(", (int)command, "), ",
"PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_CMD_SM) - (int)STATE.pio_cmd_clk_offset,
" RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM),
" TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_CMD_SM));
EMSG_PRINTF("%s: Timeout waiting for response in rp2040_sdio_command_R1(0x%hx)\n", __func__, command);
}
// Reset the state machine program
pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM);
pio_sm_exec(SDIO_PIO, SDIO_CMD_SM, pio_encode_jmp(STATE.pio_cmd_clk_offset));
return SDIO_ERR_RESPONSE_TIMEOUT;
}
}
if (response)
{
// Read out response packet
uint32_t resp0 = pio_sm_get(SDIO_PIO, SDIO_CMD_SM);
uint32_t resp1 = pio_sm_get(SDIO_PIO, SDIO_CMD_SM);
// azdbg("SDIO R1 response: ", resp0, " ", resp1);
// Calculate response checksum
uint8_t crc = 0;
crc = crc7_table[crc ^ ((resp0 >> 24) & 0xFF)];
crc = crc7_table[crc ^ ((resp0 >> 16) & 0xFF)];
crc = crc7_table[crc ^ ((resp0 >> 8) & 0xFF)];
crc = crc7_table[crc ^ ((resp0 >> 0) & 0xFF)];
crc = crc7_table[crc ^ ((resp1 >> 8) & 0xFF)];
uint8_t actual_crc = ((resp1 >> 0) & 0xFE);
if (crc != actual_crc)
{
// azdbg("rp2040_sdio_command_R1(", (int)command, "): CRC error, calculated ", crc, " packet has ", actual_crc);
EMSG_PRINTF("rp2040_sdio_command_R1(%d): CRC error, calculated 0x%hx, packet has 0x%hx\n", command, crc, actual_crc);
return SDIO_ERR_RESPONSE_CRC;
}
uint8_t response_cmd = ((resp0 >> 24) & 0xFF);
if (response_cmd != command && command != 41)
{
// azdbg("rp2040_sdio_command_R1(", (int)command, "): received reply for ", (int)response_cmd);
EMSG_PRINTF("%d rp2040_sdio_command_R1(%d): received reply for %d\n", __LINE__, command, response_cmd);
return SDIO_ERR_RESPONSE_CODE;
}
*response = ((resp0 & 0xFFFFFF) << 8) | ((resp1 >> 8) & 0xFF);
}
else
{
// Read out dummy marker
pio_sm_get(SDIO_PIO, SDIO_CMD_SM);
}
return SDIO_OK;
}
sdio_status_t rp2040_sdio_command_R2(const sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint8_t *response)
{
// The response is too long to fit in the PIO FIFO, so use DMA to receive it.
pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM);
uint32_t response_buf[5];
dma_channel_config dmacfg = dma_channel_get_default_config(SDIO_DMA_CH);
channel_config_set_transfer_data_size(&dmacfg, DMA_SIZE_32);
channel_config_set_read_increment(&dmacfg, false);
channel_config_set_write_increment(&dmacfg, true);
channel_config_set_dreq(&dmacfg, pio_get_dreq(SDIO_PIO, SDIO_CMD_SM, false));
dma_channel_configure(SDIO_DMA_CH, &dmacfg, &response_buf, &SDIO_PIO->rxf[SDIO_CMD_SM], 5, true);
sdio_send_command(sd_card_p, command, arg, 136);
uint32_t start = millis();
while (dma_channel_is_busy(SDIO_DMA_CH))
{
if ((uint32_t)(millis() - start) > sd_timeouts.rp2040_sdio_command_R2)
{
azdbg("Timeout waiting for response in rp2040_sdio_command_R2(", (int)command, "), ",
"PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_CMD_SM) - (int)STATE.pio_cmd_clk_offset,
" RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM),
" TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_CMD_SM));
// Reset the state machine program
dma_channel_abort(SDIO_DMA_CH);
pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM);
pio_sm_exec(SDIO_PIO, SDIO_CMD_SM, pio_encode_jmp(STATE.pio_cmd_clk_offset));
return SDIO_ERR_RESPONSE_TIMEOUT;
}
}
dma_channel_abort(SDIO_DMA_CH);
// Copy the response payload to output buffer
response[0] = ((response_buf[0] >> 16) & 0xFF);
response[1] = ((response_buf[0] >> 8) & 0xFF);
response[2] = ((response_buf[0] >> 0) & 0xFF);
response[3] = ((response_buf[1] >> 24) & 0xFF);
response[4] = ((response_buf[1] >> 16) & 0xFF);
response[5] = ((response_buf[1] >> 8) & 0xFF);
response[6] = ((response_buf[1] >> 0) & 0xFF);
response[7] = ((response_buf[2] >> 24) & 0xFF);
response[8] = ((response_buf[2] >> 16) & 0xFF);
response[9] = ((response_buf[2] >> 8) & 0xFF);
response[10] = ((response_buf[2] >> 0) & 0xFF);
response[11] = ((response_buf[3] >> 24) & 0xFF);
response[12] = ((response_buf[3] >> 16) & 0xFF);
response[13] = ((response_buf[3] >> 8) & 0xFF);
response[14] = ((response_buf[3] >> 0) & 0xFF);
response[15] = ((response_buf[4] >> 0) & 0xFF);
// Calculate checksum of the payload
uint8_t crc = 0;
for (int i = 0; i < 15; i++)
{
crc = crc7_table[crc ^ response[i]];
}
uint8_t actual_crc = response[15] & 0xFE;
if (crc != actual_crc)
{
azdbg("rp2040_sdio_command_R2(", (int)command, "): CRC error, calculated ", crc, " packet has ", actual_crc);
return SDIO_ERR_RESPONSE_CRC;
}
uint8_t response_cmd = ((response_buf[0] >> 24) & 0xFF);
if (response_cmd != 0x3F)
{
azdbg("rp2040_sdio_command_R2(", (int)command, "): Expected reply code 0x3F");
return SDIO_ERR_RESPONSE_CODE;
}
return SDIO_OK;
}
sdio_status_t rp2040_sdio_command_R3(sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint32_t *response)
{
sdio_send_command(sd_card_p, command, arg, 48);
// Wait for response
uint32_t start = millis();
while (pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM) < 2)
{
if ((uint32_t)(millis() - start) > sd_timeouts.rp2040_sdio_command_R3)
{
azdbg("Timeout waiting for response in rp2040_sdio_command_R3(", (int)command, "), ",
"PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_CMD_SM) - (int)STATE.pio_cmd_clk_offset,
" RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM),
" TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_CMD_SM));
// Reset the state machine program
pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM);
pio_sm_exec(SDIO_PIO, SDIO_CMD_SM, pio_encode_jmp(STATE.pio_cmd_clk_offset));
return SDIO_ERR_RESPONSE_TIMEOUT;
}
}
// Read out response packet
uint32_t resp0 = pio_sm_get(SDIO_PIO, SDIO_CMD_SM);
uint32_t resp1 = pio_sm_get(SDIO_PIO, SDIO_CMD_SM);
*response = ((resp0 & 0xFFFFFF) << 8) | ((resp1 >> 8) & 0xFF);
// azdbg("SDIO R3 response: ", resp0, " ", resp1);
return SDIO_OK;
}
/*******************************************************
* Data reception from SD card
*******************************************************/
sdio_status_t rp2040_sdio_rx_start(sd_card_t *sd_card_p, uint8_t *buffer, uint32_t num_blocks, size_t block_size)
{
// Buffer must be aligned
assert(((uint32_t)buffer & 3) == 0 && num_blocks <= SDIO_MAX_BLOCKS);
STATE.transfer_state = SDIO_RX;
STATE.transfer_start_time = millis();
STATE.data_buf = (uint32_t*)buffer;
STATE.blocks_done = 0;
STATE.total_blocks = num_blocks;
STATE.blocks_checksumed = 0;
STATE.checksum_errors = 0;
// Create DMA block descriptors to store each block of 512 bytes of data to buffer
// and then 8 bytes to STATE.received_checksums.
for (uint32_t i = 0; i < num_blocks; i++)
{
STATE.dma_blocks[i * 2].write_addr = buffer + i * block_size;
STATE.dma_blocks[i * 2].transfer_count = block_size / sizeof(uint32_t);
STATE.dma_blocks[i * 2 + 1].write_addr = &STATE.received_checksums[i];
STATE.dma_blocks[i * 2 + 1].transfer_count = 2;
}
STATE.dma_blocks[num_blocks * 2].write_addr = 0;
STATE.dma_blocks[num_blocks * 2].transfer_count = 0;
// Configure first DMA channel for reading from the PIO RX fifo
dma_channel_config dmacfg = dma_channel_get_default_config(SDIO_DMA_CH);
channel_config_set_transfer_data_size(&dmacfg, DMA_SIZE_32);
channel_config_set_read_increment(&dmacfg, false);
channel_config_set_write_increment(&dmacfg, true);
channel_config_set_dreq(&dmacfg, pio_get_dreq(SDIO_PIO, SDIO_DATA_SM, false));
channel_config_set_bswap(&dmacfg, true);
channel_config_set_chain_to(&dmacfg, SDIO_DMA_CHB);
dma_channel_configure(SDIO_DMA_CH, &dmacfg, 0, &SDIO_PIO->rxf[SDIO_DATA_SM], 0, false);
// Configure second DMA channel for reconfiguring the first one
dmacfg = dma_channel_get_default_config(SDIO_DMA_CHB);
channel_config_set_transfer_data_size(&dmacfg, DMA_SIZE_32);
channel_config_set_read_increment(&dmacfg, true);
channel_config_set_write_increment(&dmacfg, true);
channel_config_set_ring(&dmacfg, true, 3);
dma_channel_configure(SDIO_DMA_CHB, &dmacfg, &dma_hw->ch[SDIO_DMA_CH].al1_write_addr,
STATE.dma_blocks, 2, false);
// Initialize PIO state machine
pio_sm_init(SDIO_PIO, SDIO_DATA_SM, STATE.pio_data_rx_offset, &STATE.pio_cfg_data_rx);
pio_sm_set_consecutive_pindirs(SDIO_PIO, SDIO_DATA_SM, SDIO_D0, 4, false);
// Write number of nibbles to receive to Y register
pio_sm_put(SDIO_PIO, SDIO_DATA_SM, block_size * 2 + 16 - 1);
pio_sm_exec(SDIO_PIO, SDIO_DATA_SM, pio_encode_out(pio_y, 32));
// Enable RX FIFO join because we don't need the TX FIFO during transfer.
// This gives more leeway for the DMA block switching
SDIO_PIO->sm[SDIO_DATA_SM].shiftctrl |= PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS;
// Start PIO and DMA
dma_channel_start(SDIO_DMA_CHB);
pio_sm_set_enabled(SDIO_PIO, SDIO_DATA_SM, true);
return SDIO_OK;
}
// Check checksums for received blocks
static void sdio_verify_rx_checksums(sd_card_t *sd_card_p, uint32_t maxcount, size_t block_size_words)
{
while (STATE.blocks_checksumed < STATE.blocks_done && maxcount-- > 0)
{
// Calculate checksum from received data
int blockidx = STATE.blocks_checksumed++;
uint64_t checksum = sdio_crc16_4bit_checksum(STATE.data_buf + blockidx * block_size_words,
block_size_words);
// Convert received checksum to little-endian format
uint32_t top = __builtin_bswap32(STATE.received_checksums[blockidx].top);
uint32_t bottom = __builtin_bswap32(STATE.received_checksums[blockidx].bottom);
uint64_t expected = ((uint64_t)top << 32) | bottom;
if (checksum != expected)
{
STATE.checksum_errors++;
if (STATE.checksum_errors == 1)
{
EMSG_PRINTF("SDIO checksum error in reception: block %d calculated 0x%llx expected 0x%llx\n",
blockidx, checksum, expected);
dump_bytes(block_size_words, (uint8_t *)STATE.data_buf + blockidx * block_size_words);
}
}
}
}
sdio_status_t rp2040_sdio_rx_poll(sd_card_t *sd_card_p, size_t block_size_words)
{
// Was everything done when the previous rx_poll() finished?
if (STATE.blocks_done >= STATE.total_blocks)
{
STATE.transfer_state = SDIO_IDLE;
}
else
{
// Use the idle time to calculate checksums
sdio_verify_rx_checksums(sd_card_p, 4, block_size_words);
// Check how many DMA control blocks have been consumed
uint32_t dma_ctrl_block_count = (dma_hw->ch[SDIO_DMA_CHB].read_addr - (uint32_t)&STATE.dma_blocks);
dma_ctrl_block_count /= sizeof(STATE.dma_blocks[0]);
// Compute how many complete SDIO blocks have been transferred
// When transfer ends, dma_ctrl_block_count == STATE.total_blocks * 2 + 1
STATE.blocks_done = (dma_ctrl_block_count - 1) / 2;
// NOTE: When all blocks are done, rx_poll() still returns SDIO_BUSY once.
// This provides a chance to start the SCSI transfer before the last checksums
// are computed. Any checksum failures can be indicated in SCSI status after
// the data transfer has finished.
}
if (STATE.transfer_state == SDIO_IDLE)
{
// Verify all remaining checksums.
sdio_verify_rx_checksums(sd_card_p, STATE.total_blocks, block_size_words);
if (STATE.checksum_errors == 0)
return SDIO_OK;
else
return SDIO_ERR_DATA_CRC;
}
else if (millis() - STATE.transfer_start_time >= sd_timeouts.rp2040_sdio_rx_poll)
{
azdbg("rp2040_sdio_rx_poll() timeout, "
"PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_DATA_SM) - (int)STATE.pio_data_rx_offset,
" RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_DATA_SM),
" TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_DATA_SM),
" DMA CNT: ", dma_hw->ch[SDIO_DMA_CH].al2_transfer_count);
rp2040_sdio_stop(sd_card_p);
return SDIO_ERR_DATA_TIMEOUT;
}
return SDIO_BUSY;
}
/*******************************************************
* Data transmission to SD card
*******************************************************/
static void sdio_start_next_block_tx(sd_card_t *sd_card_p)
{
// Initialize PIO
pio_sm_init(SDIO_PIO, SDIO_DATA_SM, STATE.pio_data_tx_offset, &STATE.pio_cfg_data_tx);
// Configure DMA to send the data block payload (512 bytes)
dma_channel_config dmacfg = dma_channel_get_default_config(SDIO_DMA_CH);
channel_config_set_transfer_data_size(&dmacfg, DMA_SIZE_32);
channel_config_set_read_increment(&dmacfg, true);
channel_config_set_write_increment(&dmacfg, false);
channel_config_set_dreq(&dmacfg, pio_get_dreq(SDIO_PIO, SDIO_DATA_SM, true));
channel_config_set_bswap(&dmacfg, true);
channel_config_set_chain_to(&dmacfg, SDIO_DMA_CHB);
dma_channel_configure(SDIO_DMA_CH, &dmacfg,
&SDIO_PIO->txf[SDIO_DATA_SM], STATE.data_buf + STATE.blocks_done * SDIO_WORDS_PER_BLOCK,
SDIO_WORDS_PER_BLOCK, false);
// Prepare second DMA channel to send the CRC and block end marker
uint64_t crc = STATE.next_wr_block_checksum;
STATE.end_token_buf[0] = (uint32_t)(crc >> 32);
STATE.end_token_buf[1] = (uint32_t)(crc >> 0);
STATE.end_token_buf[2] = 0xFFFFFFFF;
channel_config_set_bswap(&dmacfg, false);
dma_channel_configure(SDIO_DMA_CHB, &dmacfg,
&SDIO_PIO->txf[SDIO_DATA_SM], STATE.end_token_buf, 3, false);
// Enable IRQ to trigger when block is done
switch (sd_card_p->sdio_if_p->DMA_IRQ_num) {
case DMA_IRQ_0:
// Clear any pending interrupt service request:
dma_hw->ints0 = 1 << SDIO_DMA_CHB;
dma_channel_set_irq0_enabled(SDIO_DMA_CHB, true);
break;
case DMA_IRQ_1:
// Clear any pending interrupt service request:
dma_hw->ints1 = 1 << SDIO_DMA_CHB;
dma_channel_set_irq1_enabled(SDIO_DMA_CHB, true);
break;
default:
assert(false);
}
// Initialize register X with nibble count and register Y with response bit count
pio_sm_put(SDIO_PIO, SDIO_DATA_SM, 1048);
pio_sm_exec(SDIO_PIO, SDIO_DATA_SM, pio_encode_out(pio_x, 32));
pio_sm_put(SDIO_PIO, SDIO_DATA_SM, 31);
pio_sm_exec(SDIO_PIO, SDIO_DATA_SM, pio_encode_out(pio_y, 32));
// Initialize pins to output and high
pio_sm_exec(SDIO_PIO, SDIO_DATA_SM, pio_encode_set(pio_pins, 15));
pio_sm_exec(SDIO_PIO, SDIO_DATA_SM, pio_encode_set(pio_pindirs, 15));
// Write start token and start the DMA transfer.
pio_sm_put(SDIO_PIO, SDIO_DATA_SM, 0xFFFFFFF0);
dma_channel_start(SDIO_DMA_CH);
// Start state machine
pio_sm_set_enabled(SDIO_PIO, SDIO_DATA_SM, true);
}
static void sdio_compute_next_tx_checksum(sd_card_t *sd_card_p)
{
assert (STATE.blocks_done < STATE.total_blocks && STATE.blocks_checksumed < STATE.total_blocks);
int blockidx = STATE.blocks_checksumed++;
STATE.next_wr_block_checksum = sdio_crc16_4bit_checksum(STATE.data_buf + blockidx * SDIO_WORDS_PER_BLOCK,
SDIO_WORDS_PER_BLOCK);
}
// Start transferring data from memory to SD card
sdio_status_t rp2040_sdio_tx_start(sd_card_t *sd_card_p, const uint8_t *buffer, uint32_t num_blocks)
{
// Buffer must be aligned
assert(((uint32_t)buffer & 3) == 0 && num_blocks <= SDIO_MAX_BLOCKS);
STATE.transfer_state = SDIO_TX;
STATE.transfer_start_time = millis();
STATE.data_buf = (uint32_t*)buffer;
STATE.blocks_done = 0;
STATE.total_blocks = num_blocks;
STATE.blocks_checksumed = 0;
STATE.checksum_errors = 0;
// Compute first block checksum
sdio_compute_next_tx_checksum(sd_card_p);
// Start first DMA transfer and PIO
sdio_start_next_block_tx(sd_card_p);
if (STATE.blocks_checksumed < STATE.total_blocks)
{
// Precompute second block checksum
sdio_compute_next_tx_checksum(sd_card_p);
}
return SDIO_OK;
}
static sdio_status_t check_sdio_write_response(uint32_t card_response)
{
// Shift card response until top bit is 0 (the start bit)
// The format of response is poorly documented in SDIO spec but refer to e.g.
// http://my-cool-projects.blogspot.com/2013/02/the-mysterious-sd-card-crc-status.html
uint32_t resp = card_response;
if (!(~resp & 0xFFFF0000)) resp <<= 16;
if (!(~resp & 0xFF000000)) resp <<= 8;
if (!(~resp & 0xF0000000)) resp <<= 4;
if (!(~resp & 0xC0000000)) resp <<= 2;
if (!(~resp & 0x80000000)) resp <<= 1;
uint32_t wr_status = (resp >> 28) & 7;
if (wr_status == 2)
{
return SDIO_OK;
}
else if (wr_status == 5)
{
EMSG_PRINTF("SDIO card reports write CRC error, status %lx\n", card_response);
return SDIO_ERR_WRITE_CRC;
}
else if (wr_status == 6)
{
EMSG_PRINTF("SDIO card reports write failure, status %lx\n", card_response);
return SDIO_ERR_WRITE_FAIL;
}
else
{
EMSG_PRINTF("SDIO card reports unknown write status %lx\n", card_response);
return SDIO_ERR_WRITE_FAIL;
}
}
// When a block finishes, this IRQ handler starts the next one
void sdio_irq_handler(sd_card_t *sd_card_p) {
if (STATE.transfer_state == SDIO_TX)
{
if (!dma_channel_is_busy(SDIO_DMA_CH) && !dma_channel_is_busy(SDIO_DMA_CHB))
{
// Main data transfer is finished now.
// When card is ready, PIO will put card response on RX fifo
STATE.transfer_state = SDIO_TX_WAIT_IDLE;
if (!pio_sm_is_rx_fifo_empty(SDIO_PIO, SDIO_DATA_SM))
{
// Card is already idle
STATE.card_response = pio_sm_get(SDIO_PIO, SDIO_DATA_SM);
}
else
{
// Use DMA to wait for the response
dma_channel_config dmacfg = dma_channel_get_default_config(SDIO_DMA_CHB);
channel_config_set_transfer_data_size(&dmacfg, DMA_SIZE_32);
channel_config_set_read_increment(&dmacfg, false);
channel_config_set_write_increment(&dmacfg, false);
channel_config_set_dreq(&dmacfg, pio_get_dreq(SDIO_PIO, SDIO_DATA_SM, false));
dma_channel_configure(SDIO_DMA_CHB, &dmacfg,
&STATE.card_response, &SDIO_PIO->rxf[SDIO_DATA_SM], 1, true);
}
}
}
if (STATE.transfer_state == SDIO_TX_WAIT_IDLE)
{
if (!dma_channel_is_busy(SDIO_DMA_CHB))
{
STATE.wr_status = check_sdio_write_response(STATE.card_response);
if (STATE.wr_status != SDIO_OK)
{
rp2040_sdio_stop(sd_card_p);
return;
}
STATE.blocks_done++;
if (STATE.blocks_done < STATE.total_blocks)
{
sdio_start_next_block_tx(sd_card_p);
STATE.transfer_state = SDIO_TX;
if (STATE.blocks_checksumed < STATE.total_blocks)
{
// Precompute the CRC for next block so that it is ready when
// we want to send it.
sdio_compute_next_tx_checksum(sd_card_p);
}
}
else
{
rp2040_sdio_stop(sd_card_p);
}
}
}
}
// Check if transmission is complete
sdio_status_t rp2040_sdio_tx_poll(sd_card_t *sd_card_p, uint32_t *bytes_complete)
{
if (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk)
{
// Verify that IRQ handler gets called even if we are in hardfault handler
sdio_irq_handler(sd_card_p);
}
if (bytes_complete)
{
*bytes_complete = STATE.blocks_done * SDIO_BLOCK_SIZE;
}
if (STATE.transfer_state == SDIO_IDLE)
{
rp2040_sdio_stop(sd_card_p);
return STATE.wr_status;
}
else if (millis() - STATE.transfer_start_time >= sd_timeouts.rp2040_sdio_tx_poll)
{
EMSG_PRINTF("rp2040_sdio_tx_poll() timeout\n");
DBG_PRINTF("rp2040_sdio_tx_poll() timeout, "
"PIO PC: %d"
" RXF: %d"
" TXF: %d"
" DMA CNT: %lu\n",
(int)pio_sm_get_pc(SDIO_PIO, SDIO_DATA_SM) - (int)STATE.pio_data_tx_offset,
(int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_DATA_SM),
(int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_DATA_SM),
dma_hw->ch[SDIO_DMA_CH].al2_transfer_count
);
rp2040_sdio_stop(sd_card_p);
return SDIO_ERR_DATA_TIMEOUT;
}
return SDIO_BUSY;
}
// Force everything to idle state
static sdio_status_t rp2040_sdio_stop(sd_card_t *sd_card_p)
{
dma_channel_abort(SDIO_DMA_CH);
dma_channel_abort(SDIO_DMA_CHB);
switch (sd_card_p->sdio_if_p->DMA_IRQ_num) {
case DMA_IRQ_0:
dma_channel_set_irq0_enabled(SDIO_DMA_CHB, false);
break;
case DMA_IRQ_1:
dma_channel_set_irq1_enabled(SDIO_DMA_CHB, false);
break;
default:
myASSERT(false);
}
pio_sm_set_enabled(SDIO_PIO, SDIO_DATA_SM, false);
pio_sm_set_consecutive_pindirs(SDIO_PIO, SDIO_DATA_SM, SDIO_D0, 4, false);
STATE.transfer_state = SDIO_IDLE;
return SDIO_OK;
}
bool rp2040_sdio_init(sd_card_t *sd_card_p, float clk_div) {
// Mark resources as being in use, unless it has been done already.
if (!STATE.resources_claimed) {
if (!SDIO_PIO)
SDIO_PIO = pio0; // Default
if (!sd_card_p->sdio_if_p->DMA_IRQ_num)
sd_card_p->sdio_if_p->DMA_IRQ_num = DMA_IRQ_0; // Default
// pio_sm_claim(SDIO_PIO, SDIO_CMD_SM);
// int pio_claim_unused_sm(PIO pio, bool required);
SDIO_CMD_SM = pio_claim_unused_sm(SDIO_PIO, true);
// pio_sm_claim(SDIO_PIO, SDIO_DATA_SM);
SDIO_DATA_SM = pio_claim_unused_sm(SDIO_PIO, true);
// dma_channel_claim(SDIO_DMA_CH);
SDIO_DMA_CH = dma_claim_unused_channel(true);
// dma_channel_claim(SDIO_DMA_CHB);
SDIO_DMA_CHB = dma_claim_unused_channel(true);
/* Set up IRQ handler for when DMA completes. */
dma_irq_add_handler(sd_card_p->sdio_if_p->DMA_IRQ_num,
sd_card_p->sdio_if_p->use_exclusive_DMA_IRQ_handler);
STATE.resources_claimed = true;
}
dma_channel_abort(SDIO_DMA_CH);
dma_channel_abort(SDIO_DMA_CHB);
pio_sm_set_enabled(SDIO_PIO, SDIO_CMD_SM, false);
pio_sm_set_enabled(SDIO_PIO, SDIO_DATA_SM, false);
// Load PIO programs
pio_clear_instruction_memory(SDIO_PIO);
// Command & clock state machine
STATE.pio_cmd_clk_offset = pio_add_program(SDIO_PIO, &sdio_cmd_clk_program);
pio_sm_config cfg = sdio_cmd_clk_program_get_default_config(STATE.pio_cmd_clk_offset);
sm_config_set_out_pins(&cfg, SDIO_CMD, 1);
sm_config_set_in_pins(&cfg, SDIO_CMD);
sm_config_set_set_pins(&cfg, SDIO_CMD, 1);
sm_config_set_jmp_pin(&cfg, SDIO_CMD);
sm_config_set_sideset_pins(&cfg, SDIO_CLK);
sm_config_set_out_shift(&cfg, false, true, 32);
sm_config_set_in_shift(&cfg, false, true, 32);
sm_config_set_clkdiv(&cfg, clk_div);
sm_config_set_mov_status(&cfg, STATUS_TX_LESSTHAN, 2);
pio_sm_init(SDIO_PIO, SDIO_CMD_SM, STATE.pio_cmd_clk_offset, &cfg);
pio_sm_set_consecutive_pindirs(SDIO_PIO, SDIO_CMD_SM, SDIO_CLK, 1, true);
pio_sm_set_enabled(SDIO_PIO, SDIO_CMD_SM, true);
// Data reception program
STATE.pio_data_rx_offset = pio_add_program(SDIO_PIO, &sdio_data_rx_program);
STATE.pio_cfg_data_rx = sdio_data_rx_program_get_default_config(STATE.pio_data_rx_offset);
sm_config_set_in_pins(&STATE.pio_cfg_data_rx, SDIO_D0);
sm_config_set_in_shift(&STATE.pio_cfg_data_rx, false, true, 32);
sm_config_set_out_shift(&STATE.pio_cfg_data_rx, false, true, 32);
sm_config_set_clkdiv(&STATE.pio_cfg_data_rx, clk_div);
// Data transmission program
STATE.pio_data_tx_offset = pio_add_program(SDIO_PIO, &sdio_data_tx_program);
STATE.pio_cfg_data_tx = sdio_data_tx_program_get_default_config(STATE.pio_data_tx_offset);
sm_config_set_in_pins(&STATE.pio_cfg_data_tx, SDIO_D0);
sm_config_set_set_pins(&STATE.pio_cfg_data_tx, SDIO_D0, 4);
sm_config_set_out_pins(&STATE.pio_cfg_data_tx, SDIO_D0, 4);
sm_config_set_in_shift(&STATE.pio_cfg_data_tx, false, false, 32);
sm_config_set_out_shift(&STATE.pio_cfg_data_tx, false, true, 32);
sm_config_set_clkdiv(&STATE.pio_cfg_data_tx, clk_div);
// Disable SDIO pins input synchronizer.
// This reduces input delay.
// Because the CLK is driven synchronously to CPU clock,
// there should be no metastability problems.
SDIO_PIO->input_sync_bypass |= (1 << SDIO_CLK) | (1 << SDIO_CMD) | (1 << SDIO_D0) | (1 << SDIO_D1) | (1 << SDIO_D2) | (1 << SDIO_D3);
// Redirect GPIOs to PIO
#if PICO_SDK_VERSION_MAJOR < 2
typedef enum gpio_function gpio_function_t;
#endif
gpio_function_t fn;
if (pio1 == SDIO_PIO)
fn = GPIO_FUNC_PIO1;
else
fn = GPIO_FUNC_PIO0;
gpio_set_function(SDIO_CMD, fn);
gpio_set_function(SDIO_CLK, fn);
gpio_set_function(SDIO_D0, fn);
gpio_set_function(SDIO_D1, fn);
gpio_set_function(SDIO_D2, fn);
gpio_set_function(SDIO_D3, fn);
gpio_set_slew_rate(SDIO_CMD, GPIO_SLEW_RATE_FAST);
gpio_set_slew_rate(SDIO_CLK, GPIO_SLEW_RATE_FAST);
gpio_set_slew_rate(SDIO_D0, GPIO_SLEW_RATE_FAST);
gpio_set_slew_rate(SDIO_D1, GPIO_SLEW_RATE_FAST);
gpio_set_slew_rate(SDIO_D2, GPIO_SLEW_RATE_FAST);
gpio_set_slew_rate(SDIO_D3, GPIO_SLEW_RATE_FAST);
if (sd_card_p->sdio_if_p->set_drive_strength) {
gpio_set_drive_strength(SDIO_CMD, sd_card_p->sdio_if_p->CMD_gpio_drive_strength);
gpio_set_drive_strength(SDIO_CLK, sd_card_p->sdio_if_p->CLK_gpio_drive_strength);
gpio_set_drive_strength(SDIO_D0, sd_card_p->sdio_if_p->D0_gpio_drive_strength);
gpio_set_drive_strength(SDIO_D1, sd_card_p->sdio_if_p->D1_gpio_drive_strength);
gpio_set_drive_strength(SDIO_D2, sd_card_p->sdio_if_p->D2_gpio_drive_strength);
gpio_set_drive_strength(SDIO_D3, sd_card_p->sdio_if_p->D3_gpio_drive_strength);
}
return true;
}
// SD card access using SDIO for RP2040 platform.
// This module contains the low-level SDIO bus implementation using
// the PIO peripheral. The high-level commands are in sd_card_sdio.cpp.
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#include "sd_card.h"
//FIXME: why?
typedef struct sd_card_t sd_card_t;
typedef
enum sdio_status_t {
SDIO_OK = 0,
SDIO_BUSY = 1,
SDIO_ERR_RESPONSE_TIMEOUT = 2, // Timed out waiting for response from card
SDIO_ERR_RESPONSE_CRC = 3, // Response CRC is wrong
SDIO_ERR_RESPONSE_CODE = 4, // Response command code does not match what was sent
SDIO_ERR_DATA_TIMEOUT = 5, // Timed out waiting for data block
SDIO_ERR_DATA_CRC = 6, // CRC for data packet is wrong
SDIO_ERR_WRITE_CRC = 7, // Card reports bad CRC for write
SDIO_ERR_WRITE_FAIL = 8, // Card reports write failure
} sdio_status_t;
#define SDIO_BLOCK_SIZE 512
#define SDIO_WORDS_PER_BLOCK (SDIO_BLOCK_SIZE / 4) // 128
// Maximum number of 512 byte blocks to transfer in one request
#define SDIO_MAX_BLOCKS 256
typedef enum sdio_transfer_state_t { SDIO_IDLE, SDIO_RX, SDIO_TX, SDIO_TX_WAIT_IDLE} sdio_transfer_state_t;
typedef struct sd_sdio_if_state_t {
bool resources_claimed;
uint32_t ocr; // Operating condition register from card
uint32_t rca; // Relative card address
int error_line;
sdio_status_t error;
uint32_t dma_buf[128];
int SDIO_DMA_CH;
int SDIO_DMA_CHB;
int SDIO_CMD_SM;
int SDIO_DATA_SM;
uint32_t pio_cmd_clk_offset;
uint32_t pio_data_rx_offset;
pio_sm_config pio_cfg_data_rx;
uint32_t pio_data_tx_offset;
pio_sm_config pio_cfg_data_tx;
sdio_transfer_state_t transfer_state;
uint32_t transfer_start_time;
uint32_t *data_buf;
uint32_t blocks_done; // Number of blocks transferred so far
uint32_t total_blocks; // Total number of blocks to transfer
uint32_t blocks_checksumed; // Number of blocks that have had CRC calculated
uint32_t checksum_errors; // Number of checksum errors detected
// Variables for block writes
uint64_t next_wr_block_checksum;
uint32_t end_token_buf[3]; // CRC and end token for write block
sdio_status_t wr_status;
uint32_t card_response;
// Variables for extended block writes
bool ongoing_wr_mlt_blk;
uint32_t wr_mlt_blk_cnt_sector;
// Variables for block reads
// This is used to perform DMA into data buffers and checksum buffers separately.
struct {
void * write_addr;
uint32_t transfer_count;
} dma_blocks[SDIO_MAX_BLOCKS * 2];
struct {
uint32_t top;
uint32_t bottom;
} received_checksums[SDIO_MAX_BLOCKS];
} sd_sdio_if_state_t;
// Execute a command that has 48-bit reply (response types R1, R6, R7)
// If response is NULL, does not wait for reply.
sdio_status_t rp2040_sdio_command_R1(sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint32_t *response);
// Execute a command that has 136-bit reply (response type R2)
// Response buffer should have space for 16 bytes (the 128 bit payload)
sdio_status_t rp2040_sdio_command_R2(const sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint8_t *response);
// Execute a command that has 48-bit reply but without CRC (response R3)
sdio_status_t rp2040_sdio_command_R3(sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint32_t *response);
// Start transferring data from SD card to memory buffer
sdio_status_t rp2040_sdio_rx_start(sd_card_t *sd_card_p, uint8_t *buffer, uint32_t num_blocks, size_t block_size);
// Check if reception is complete
// Returns SDIO_BUSY while transferring, SDIO_OK when done and error on failure.
sdio_status_t rp2040_sdio_rx_poll(sd_card_t *sd_card_p, size_t block_size_words);
// Start transferring data from memory to SD card
sdio_status_t rp2040_sdio_tx_start(sd_card_t *sd_card_p, const uint8_t *buffer, uint32_t num_blocks);
// Check if transmission is complete
sdio_status_t rp2040_sdio_tx_poll(sd_card_t *sd_card_p, uint32_t *bytes_complete /* = nullptr */);
// (Re)initialize the SDIO interface
bool rp2040_sdio_init(sd_card_t *sd_card_p, float clk_div);
void __not_in_flash_func(sdio_irq_handler)(sd_card_t *sd_card_p);
#ifdef __cplusplus
}
#endif
; RP2040 PIO program for implementing SD card access in SDIO mode
; Run "pioasm rp2040_sdio.pio rp2040_sdio.pio.h" to regenerate the C header from this.
; The RP2040 official work-in-progress code at
; https://github.com/raspberrypi/pico-extras/tree/master/src/rp2_common/pico_sd_card
; may be useful reference, but this is independent implementation.
;
; For official SDIO specifications, refer to:
; https://www.sdcard.org/downloads/pls/
; "SDIO Physical Layer Simplified Specification Version 8.00"
; Clock settings
; For 3.3V communication the available speeds are:
; - Default speed: max. 25 MHz clock
; - High speed: max. 50 MHz clock
;
; From the default RP2040 clock speed of 125 MHz, the closest dividers
; are 3 for 41.7 MHz and 5 for 25 MHz. The CPU can apply further divider
; through state machine registers for the initial handshake.
;
; Because data is written on the falling edge and read on the rising
; edge, it is preferrable to have a long 0 state and short 1 state.
;.define CLKDIV 3
;.define CLKDIV 5
;.define D0 ((CLKDIV + 1) / 2 - 1)
;.define D1 (CLKDIV/2 - 1)
.define D0 1
.define D1 1
.define PUBLIC CLKDIV D0 + 1 + D1 + 1
; .define PUBLIC SDIO_CLK_GPIO 17
; This is relative to D0 GPIO number.
; The pin is selected by adding Index to the
; PINCTRL_IN_BASE configuration, modulo 32.
; This is used as a WAIT index, and must be between 4 and 31.
; (Offsets 0-3 are D0, D1, D2, and D3.)
.define PUBLIC SDIO_CLK_PIN_D0_OFFSET 18 ; (-2 in mod32 arithmetic)
; State machine 0 is used to:
; - generate continuous clock on SDIO_CLK
; - send CMD packets
; - receive response packets
;
; Pin mapping for this state machine:
; - Sideset : CLK
; - IN/OUT/SET : CMD
; - JMP_PIN : CMD
;
; The commands to send are put on TX fifo and must have two words:
; Word 0 bits 31-24: Number of bits in command minus one (usually 47)
; Word 0 bits 23-00: First 24 bits of the command packet, shifted out MSB first
; Word 1 bits 31-08: Last 24 bits of the command packet, shifted out MSB first
; Word 1 bits 07-00: Number of bits in response minus one (usually 47), or 0 if no response
;
; The response is put on RX fifo, starting with the MSB.
; Partial last word will be padded with zero bits at the top.
;
; The state machine EXECCTRL should be set so that STATUS indicates TX FIFO < 2
; and that AUTOPULL and AUTOPUSH are enabled.
.program sdio_cmd_clk
.side_set 1
mov OSR, NULL side 1 [D1] ; Make sure OSR is full of zeros to prevent autopull
wait_cmd:
mov Y, !STATUS side 0 [D0] ; Check if TX FIFO has data
jmp !Y wait_cmd side 1 [D1]
load_cmd:
out NULL, 32 side 0 [D0] ; Load first word (trigger autopull)
out X, 8 side 1 [D1] ; Number of bits to send
set pins, 1 side 0 [D0] ; Initial state of CMD is high
set pindirs, 1 side 1 [D1] ; Set SDIO_CMD as output
send_cmd:
out pins, 1 side 0 [D0] ; Write output on falling edge of CLK
jmp X-- send_cmd side 1 [D1]
prep_resp:
set pindirs, 0 side 0 [D0] ; Set SDIO_CMD as input
out X, 8 side 1 [D1] ; Get number of bits in response
nop side 0 [D0] ; For clock alignment
jmp !X resp_done side 1 [D1] ; Check if we expect a response
wait_resp:
nop side 0 [D0]
jmp PIN wait_resp side 1 [D1] ; Loop until SDIO_CMD = 0
; Note: input bits are read at the same time as we write CLK=0.
; Because the host controls the clock, the read happens before
; the card sees the falling clock edge. This gives maximum time
; for the data bit to settle.
read_resp:
in PINS, 1 side 0 [D0] ; Read input data bit
jmp X-- read_resp side 1 [D1] ; Loop to receive all data bits
resp_done:
push side 0 [D0] ; Push the remaining part of response
; State machine 1 is used to send and receive data blocks.
; Pin mapping for this state machine:
; - IN / OUT: SDIO_D0-D3
; - GPIO defined at beginning of this file: SDIO_CLK
; Data reception program
; This program will wait for initial start of block token and then
; receive a data block. The application must set number of nibbles
; to receive minus 1 to Y register before running this program.
.program sdio_data_rx
wait_start:
mov X, Y ; Reinitialize number of nibbles to receive
wait 0 pin 0 ; Wait for zero state on D0
wait 1 pin SDIO_CLK_PIN_D0_OFFSET [CLKDIV-1] ; Wait for rising edge and then whole clock cycle
rx_data:
in PINS, 4 [CLKDIV-2] ; Read nibble
jmp X--, rx_data
; Data transmission program
;
; Before running this program, pindirs should be set as output
; and register X should be initialized with the number of nibbles
; to send minus 1 (typically 8 + 1024 + 16 + 1 - 1 = 1048)
; and register Y with the number of response bits minus 1 (typically 31).
;
; Words written to TX FIFO must be:
; - Word 0: start token 0xFFFFFFF0
; - Word 1-128: transmitted data (512 bytes)
; - Word 129-130: CRC checksum
; - Word 131: end token 0xFFFFFFFF
;
; After the card reports idle status, RX FIFO will get a word that
; contains the D0 line response from card.
.program sdio_data_tx
wait 0 pin SDIO_CLK_PIN_D0_OFFSET
wait 1 pin SDIO_CLK_PIN_D0_OFFSET [CLKDIV + D1 - 1]; Synchronize so that write occurs on falling edge
tx_loop:
out PINS, 4 [D0] ; Write nibble and wait for whole clock cycle
jmp X-- tx_loop [D1]
set pindirs, 0x00 [D0] ; Set data bus as input
.wrap_target
response_loop:
in PINS, 1 [D1] ; Read D0 on rising edge
jmp Y--, response_loop [D0]
wait_idle:
wait 1 pin 0 [D1] ; Wait for card to indicate idle condition
push [D0] ; Push the response token
.wrap
\ No newline at end of file
// Driver for accessing SD card in SDIO mode on RP2040.
#include "ZuluSCSI_platform.h"
#include <stdint.h>
#include <string.h>
//
// Hardware
//
#include <hardware/dma.h>
#include <hardware/gpio.h>
#include <hardware/clocks.h>
#include <hardware/pio.h>
//
// Platform
//
#include "pico/stdlib.h"
//
// Project
//
#include "diskio.h"
#include "my_debug.h"
#include "delays.h"
#include "rp2040_sdio.h"
#include "rp2040_sdio.pio.h" // build\build\rp2040_sdio.pio.h
#include "sd_card_constants.h"
#include "sd_card.h"
#include "sd_timeouts.h"
#include "SdioCard.h"
#include "util.h"
#define STATE sd_card_p->sdio_if_p->state
static char const *errstr(sdio_status_t error) {
switch (error) {
case SDIO_OK:
return "SDIO: OK";
case SDIO_BUSY:
return "SDIO: busy";
case SDIO_ERR_RESPONSE_TIMEOUT:
return "SDIO: Timed out waiting for response from card";
case SDIO_ERR_RESPONSE_CRC:
return "SDIO: Response CRC is wrong";
case SDIO_ERR_RESPONSE_CODE:
return "SDIO: Response command code does not match what was sent";
case SDIO_ERR_DATA_TIMEOUT:
return "SDIO: Timed out waiting for data block";
case SDIO_ERR_DATA_CRC:
return "SDIO: CRC for data packet is wrong";
case SDIO_ERR_WRITE_CRC:
return "SDIO: Card reports bad CRC for write";
case SDIO_ERR_WRITE_FAIL:
return "SDIO: Card reports write failure";
}
return "Unknown error";
}
//FIXME
#define azdbg(arg1, ...) {\
DBG_PRINTF("%s,%d: %s\n", __func__, __LINE__, arg1); \
}
#define TRACE_PRINTF(fmt, args...)
//#define TRACE_PRINTF DBG_PRINTF
#define checkReturnOk(call) ((STATE.error = (call)) == SDIO_OK ? true : logSDError(sd_card_p, __LINE__))
static bool logSDError(sd_card_t *sd_card_p, int line)
{
STATE.error_line = line;
EMSG_PRINTF("%s at line %d; error code %d\n",
errstr(STATE.error), line, (int)STATE.error);
return false;
}
/*
CLKDIV is from sd_driver\SDIO\rp2040_sdio.pio
baud = clk_sys / (CLKDIV * clk_div)
baud * CLKDIV * clk_div = clk_sys;
clk_div = clk_sys / (CLKDIV * baud)
*/
static float calculate_clk_div(uint baud) {
float div = (float)clock_get_hz(clk_sys) / (CLKDIV * baud);
/* Baud rate cannot exceed clk_sys frequency divided by CLKDIV! */
myASSERT(div >= 1 && div <= 65536);
return div;
}
bool sd_sdio_begin(sd_card_t *sd_card_p)
{
uint32_t reply;
sdio_status_t status;
// Initialize at 400 kHz clock speed
if (!rp2040_sdio_init(sd_card_p, calculate_clk_div(400 * 1000)))
return false;
// Establish initial connection with the card
for (int retries = 0; retries < 5; retries++)
{
delay_ms(1);
reply = 0;
rp2040_sdio_command_R1(sd_card_p, CMD0_GO_IDLE_STATE, 0, NULL); // GO_IDLE_STATE
status = rp2040_sdio_command_R1(sd_card_p, CMD8_SEND_IF_COND, 0x1AA, &reply); // SEND_IF_COND
if (status == SDIO_OK && reply == 0x1AA)
{
break;
}
}
if (reply != 0x1AA || status != SDIO_OK)
{
// azdbg("SDIO not responding to CMD8 SEND_IF_COND, status ", (int)status, " reply ", reply);
EMSG_PRINTF("%s,%d SDIO not responding to CMD8 SEND_IF_COND, status 0x%x reply 0x%lx\n",
__func__, __LINE__, status, reply);
return false;
}
// Send ACMD41 to begin card initialization and wait for it to complete
uint32_t start = millis();
do {
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, 0, &reply)) || // APP_CMD
!checkReturnOk(rp2040_sdio_command_R3(sd_card_p, ACMD41_SD_SEND_OP_COND, 0xD0040000, &STATE.ocr))) // 3.0V voltage
// !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD41, 0xC0100000, &STATE.ocr)))
{
return false;
}
if ((uint32_t)(millis() - start) > sd_timeouts.sd_sdio_begin)
{
EMSG_PRINTF("SDIO card initialization timeout\n");
return false;
}
} while (!(STATE.ocr & (1 << 31)));
// Get CID
// CMD2 is valid only in "ready" state;
// Transitions to "ident" state
// Note: CMD10 is valid only in "stby" state
if (!checkReturnOk(rp2040_sdio_command_R2(sd_card_p, CMD2_ALL_SEND_CID, 0, (uint8_t *)&sd_card_p->state.CID)))
{
azdbg("SDIO failed to read CID");
return false;
}
// Get relative card address
// Valid in "ident" or "stby" state; transitions to "stby"
// Transitions from "card-identification-mode" to "data-transfer-mode"
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD3_SEND_RELATIVE_ADDR, 0, &STATE.rca)))
{
azdbg("SDIO failed to get RCA");
return false;
}
// Get CSD
// Valid in "stby" state; stays in "stby" state
if (!checkReturnOk(rp2040_sdio_command_R2(sd_card_p, CMD9_SEND_CSD, STATE.rca, sd_card_p->state.CSD)))
{
azdbg("SDIO failed to read CSD");
return false;
}
sd_card_p->state.sectors = CSD_sectors(sd_card_p->state.CSD);
// Select card
// Valid in "stby" state;
// If card is addressed, transitions to "tran" state
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD7_SELECT_CARD, STATE.rca, &reply)))
{
azdbg("SDIO failed to select card");
return false;
}
/* At power up CD/DAT3 has a 50KOhm pull up enabled in the card.
This resistor serves two functions Card detection and Mode Selection.
For Mode Selection, the host can drive the line high or let it be pulled high to select SD mode.
If the host wants to select SPI mode it should drive the line low.
For Card detection, the host detects that the line is pulled high.
This pull-up should be disconnected by the user, during regular data transfer,
with SET_CLR_CARD_DETECT (ACMD42) command. */
// Disconnect the 50 KOhm pull-up resistor on CD/DAT3
// Valid in "tran" state; stays in "tran" state
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, STATE.rca, &reply)) ||
!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD42_SET_CLR_CARD_DETECT, 0, &reply)))
{
azdbg("SDIO failed to disconnect pull-up");
return false;
}
// Set 4-bit bus mode
// Valid in "tran" state; stays in "tran" state
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, STATE.rca, &reply)) ||
!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD6_SET_BUS_WIDTH, 2, &reply)))
{
azdbg("SDIO failed to set bus width");
return false;
}
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply))) // SET_BLOCKLEN
{
EMSG_PRINTF("%s,%d SDIO failed to set BLOCKLEN\n", __func__, __LINE__);
return false;
}
// Increase to high clock rate
if (!sd_card_p->sdio_if_p->baud_rate)
sd_card_p->sdio_if_p->baud_rate = 10 * 1000 * 1000; // 10 MHz default
if (!rp2040_sdio_init(sd_card_p, calculate_clk_div(sd_card_p->sdio_if_p->baud_rate)))
return false;
return true;
}
uint8_t sd_sdio_errorCode(sd_card_t *sd_card_p) // const
{
return STATE.error;
}
uint32_t sd_sdio_errorData() // const
{
return 0;
}
uint32_t sd_sdio_errorLine(sd_card_t *sd_card_p) // const
{
return STATE.error_line;
}
bool sd_sdio_isBusy(sd_card_t *sd_card_p)
{
// return (sio_hw->gpio_in & (1 << SDIO_D0)) == 0;
return (sio_hw->gpio_in & (1 << sd_card_p->sdio_if_p->D0_gpio)) == 0;
}
bool sd_sdio_readOCR(sd_card_t *sd_card_p, uint32_t* ocr)
{
// SDIO mode does not have CMD58, but main program uses this to
// poll for card presence. Return status register instead.
return checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD13_SEND_STATUS, STATE.rca, ocr));
}
uint32_t sd_sdio_status(sd_card_t *sd_card_p)
{
uint32_t reply;
if (checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD13_SEND_STATUS, STATE.rca, &reply)))
return reply;
else
return 0;
}
bool sd_sdio_stopTransmission(sd_card_t *sd_card_p, bool blocking)
{
STATE.ongoing_wr_mlt_blk = false;
uint32_t reply;
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD12_STOP_TRANSMISSION, 0, &reply)))
{
return false;
}
if (!blocking)
{
return true;
}
else
{
uint32_t start = millis();
while (millis() - start < 200 && sd_sdio_isBusy(sd_card_p));
if (sd_sdio_isBusy(sd_card_p))
{
EMSG_PRINTF("sd_sdio_stopTransmission() timeout\n");
return false;
}
else
{
return true;
}
}
}
uint8_t sd_sdio_type(sd_card_t *sd_card_p) // const
{
if (STATE.ocr & (1 << 30))
return SDCARD_V2HC;
else
return SDCARD_V2;
}
/* Writing and reading */
bool sd_sdio_writeSector(sd_card_t *sd_card_p, uint32_t sector, const uint8_t* src)
{
if (STATE.ongoing_wr_mlt_blk)
// Stop any ongoing write transmission
if (!sd_sdio_stopTransmission(sd_card_p, true)) return false;
if (((uint32_t)src & 3) != 0) {
// Buffer is not aligned, need to memcpy() the data to a temporary buffer.
memcpy(STATE.dma_buf, src, sizeof(STATE.dma_buf));
src = (uint8_t*)STATE.dma_buf;
}
uint32_t reply;
if (/* !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply)) || // SET_BLOCKLEN */
!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD24_WRITE_BLOCK, sector, &reply)) || // WRITE_BLOCK
!checkReturnOk(rp2040_sdio_tx_start(sd_card_p, src, 1))) // Start transmission
{
return false;
}
do {
uint32_t bytes_done;
STATE.error = rp2040_sdio_tx_poll(sd_card_p, &bytes_done);
} while (STATE.error == SDIO_BUSY);
if (STATE.error != SDIO_OK)
{
EMSG_PRINTF("sd_sdio_writeSector(%lu) failed: %s (%d)\n",
sector, errstr(STATE.error), (int)STATE.error);
}
return STATE.error == SDIO_OK;
}
bool sd_sdio_writeSectors(sd_card_t *sd_card_p, uint32_t sector, const uint8_t *src, size_t n) {
if (((uint32_t)src & 3) != 0) {
// Unaligned write, execute sector-by-sector
for (size_t i = 0; i < n; i++) {
if (!sd_sdio_writeSector(sd_card_p, sector + i, src + 512 * i)) {
return false;
}
}
return true;
}
if (STATE.ongoing_wr_mlt_blk && sector == STATE.wr_mlt_blk_cnt_sector) {
/* Continue a multiblock write */
if (!checkReturnOk(rp2040_sdio_tx_start(sd_card_p, src, n))) // Start transmission
return false;
} else {
// Stop any previous transmission
if (STATE.ongoing_wr_mlt_blk) {
if (!sd_sdio_stopTransmission(sd_card_p, true)) return false;
}
uint32_t reply;
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD25_WRITE_MULTIPLE_BLOCK, sector, &reply)) ||
!checkReturnOk(rp2040_sdio_tx_start(sd_card_p, src, n))) // Start transmission
{
return false;
}
}
do {
uint32_t bytes_done;
STATE.error = rp2040_sdio_tx_poll(sd_card_p, &bytes_done);
} while (STATE.error == SDIO_BUSY);
if (STATE.error != SDIO_OK) {
EMSG_PRINTF("sd_sdio_writeSectors(,%lu,,%zu) failed: %s (%d)\n", sector, n, errstr(STATE.error), (int)STATE.error);
sd_sdio_stopTransmission(sd_card_p, true);
return false;
} else {
STATE.wr_mlt_blk_cnt_sector = sector + n;
STATE.ongoing_wr_mlt_blk = true;
return true;
}
/* Optimization:
To optimize large contiguous writes,
postpone stopping transmission until it is
clear that the next operation is not a continuation.
Any transactions other than a `sd_sdio_writeSectors`
continuation must stop any ongoing transmission
before proceding.
*/
}
bool sd_sdio_readSector(sd_card_t *sd_card_p, uint32_t sector, uint8_t* dst)
{
if (STATE.ongoing_wr_mlt_blk)
// Stop any ongoing transmission
if (!sd_sdio_stopTransmission(sd_card_p, true)) return false;
uint8_t *real_dst = dst;
if (((uint32_t)dst & 3) != 0)
{
// Buffer is not aligned, need to memcpy() the data from a temporary buffer.
dst = (uint8_t*)STATE.dma_buf;
}
uint32_t reply;
if (/* !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply)) || // SET_BLOCKLEN */
!checkReturnOk(rp2040_sdio_rx_start(sd_card_p, dst, 1, SDIO_BLOCK_SIZE)) || // Prepare for reception
!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD17_READ_SINGLE_BLOCK, sector, &reply))) // READ_SINGLE_BLOCK
{
return false;
}
do {
STATE.error = rp2040_sdio_rx_poll(sd_card_p, SDIO_WORDS_PER_BLOCK);
} while (STATE.error == SDIO_BUSY);
if (STATE.error != SDIO_OK)
{
EMSG_PRINTF("sd_sdio_readSector(,%lu,) failed: %s (%d)\n",
sector, errstr(STATE.error), (int)STATE.error);
}
if (dst != real_dst)
{
memcpy(real_dst, STATE.dma_buf, sizeof(STATE.dma_buf));
}
return STATE.error == SDIO_OK;
}
bool sd_sdio_readSectors(sd_card_t *sd_card_p, uint32_t sector, uint8_t* dst, size_t n)
{
if (STATE.ongoing_wr_mlt_blk)
// Stop any ongoing transmission
if (!sd_sdio_stopTransmission(sd_card_p, true)) return false;
if (((uint32_t)dst & 3) != 0 || sector + n >= sd_card_p->state.sectors)
{
// Unaligned read or end-of-drive read, execute sector-by-sector
for (size_t i = 0; i < n; i++)
{
if (!sd_sdio_readSector(sd_card_p, sector + i, dst + 512 * i))
{
return false;
}
}
return true;
}
uint32_t reply;
if (/* !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply)) || // SET_BLOCKLEN */
!checkReturnOk(rp2040_sdio_rx_start(sd_card_p, dst, n, SDIO_BLOCK_SIZE)) || // Prepare for reception
!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD18_READ_MULTIPLE_BLOCK, sector, &reply))) // READ_MULTIPLE_BLOCK
{
return false;
}
do {
STATE.error = rp2040_sdio_rx_poll(sd_card_p, SDIO_WORDS_PER_BLOCK);
} while (STATE.error == SDIO_BUSY);
if (STATE.error != SDIO_OK)
{
EMSG_PRINTF("sd_sdio_readSectors(%ld,...,%d) failed: %s (%d)\n",
sector, n, errstr(STATE.error), STATE.error);
sd_sdio_stopTransmission(sd_card_p, true);
return false;
}
else
{
return sd_sdio_stopTransmission(sd_card_p, true);
}
}
// Get 512 bit (64 byte) SD Status
bool rp2040_sdio_get_sd_status(sd_card_t *sd_card_p, uint8_t response[64]) {
uint32_t reply;
if (!checkReturnOk(rp2040_sdio_rx_start(sd_card_p, response, 1, 64)) || // Prepare for reception
!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, STATE.rca, &reply)) || // APP_CMD
!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD13_SD_STATUS, 0, &reply))) // SD Status
{
EMSG_PRINTF("ACMD13 failed\n");
return false;
}
// Read 512 bit block on DAT bus (not CMD)
do {
STATE.error = rp2040_sdio_rx_poll(sd_card_p, 64 / 4);
} while (STATE.error == SDIO_BUSY);
if (STATE.error != SDIO_OK)
{
EMSG_PRINTF("ACMD13 failed: %s (%d)\n", errstr(STATE.error), (int)STATE.error);
}
return STATE.error == SDIO_OK;
}
static bool sd_sdio_test_com(sd_card_t *sd_card_p) {
bool success = false;
if (!(sd_card_p->state.m_Status & STA_NOINIT)) {
// SD card is currently initialized
// Get status
uint32_t reply = 0;
sdio_status_t status = rp2040_sdio_command_R1(sd_card_p, CMD13_SEND_STATUS, STATE.rca, &reply);
// Only care that communication succeeded
success = (status == SDIO_OK);
if (!success) {
// Card no longer sensed - ensure card is initialized once re-attached
sd_card_p->state.m_Status |= STA_NOINIT;
}
} else {
// Do a "light" version of init, just enough to test com
// Initialize at 400 kHz clock speed
if (!rp2040_sdio_init(sd_card_p, calculate_clk_div(400 * 1000)))
return false;
// Establish initial connection with the card
rp2040_sdio_command_R1(sd_card_p, CMD0_GO_IDLE_STATE, 0, NULL); // GO_IDLE_STATE
uint32_t reply = 0;
sdio_status_t status = rp2040_sdio_command_R1(sd_card_p, CMD8_SEND_IF_COND, 0x1AA, &reply); // SEND_IF_COND
success = (reply == 0x1AA && status == SDIO_OK);
}
return success;
}
#if PICO_SDK_VERSION_MAJOR < 2
typedef enum gpio_function gpio_function_t;
#endif
// Helper function to configure whole GPIO in one line
static void gpio_conf(uint gpio, gpio_function_t fn, bool pullup, bool pulldown, bool output, bool initial_state)
{
gpio_put(gpio, initial_state);
gpio_set_dir(gpio, output);
gpio_set_pulls(gpio, pullup, pulldown);
gpio_set_function(gpio, fn);
// See rp2040_sdio_init
}
static DSTATUS sd_sdio_init(sd_card_t *sd_card_p) {
sd_lock(sd_card_p);
// Make sure there's a card in the socket before proceeding
sd_card_detect(sd_card_p);
if (sd_card_p->state.m_Status & STA_NODISK) {
sd_unlock(sd_card_p);
return sd_card_p->state.m_Status;
}
// Make sure we're not already initialized before proceeding
if (!(sd_card_p->state.m_Status & STA_NOINIT)) {
sd_unlock(sd_card_p);
return sd_card_p->state.m_Status;
}
// Initialize the member variables
sd_card_p->state.card_type = SDCARD_NONE;
// pin function pup pdown out state
gpio_conf(sd_card_p->sdio_if_p->CLK_gpio, GPIO_FUNC_PIO1, true, false, true, true);
gpio_conf(sd_card_p->sdio_if_p->CMD_gpio, GPIO_FUNC_PIO1, true, false, true, true);
gpio_conf(sd_card_p->sdio_if_p->D0_gpio, GPIO_FUNC_PIO1, true, false, false, true);
gpio_conf(sd_card_p->sdio_if_p->D1_gpio, GPIO_FUNC_PIO1, true, false, false, true);
gpio_conf(sd_card_p->sdio_if_p->D2_gpio, GPIO_FUNC_PIO1, true, false, false, true);
gpio_conf(sd_card_p->sdio_if_p->D3_gpio, GPIO_FUNC_PIO1, true, false, false, true);
bool ok = sd_sdio_begin(sd_card_p);
if (ok) {
// The card is now initialized
sd_card_p->state.m_Status &= ~STA_NOINIT;
}
sd_unlock(sd_card_p);
return sd_card_p->state.m_Status;
}
static void sd_sdio_deinit(sd_card_t *sd_card_p) {
sd_lock(sd_card_p);
sd_card_p->state.m_Status |= STA_NOINIT;
sd_card_p->state.card_type = SDCARD_NONE;
// pin function pup pdown out state
gpio_conf(sd_card_p->sdio_if_p->CLK_gpio, GPIO_FUNC_NULL, false, false, false, false);
gpio_conf(sd_card_p->sdio_if_p->CMD_gpio, GPIO_FUNC_NULL, false, false, false, false);
gpio_conf(sd_card_p->sdio_if_p->D0_gpio, GPIO_FUNC_NULL, false, false, false, false);
gpio_conf(sd_card_p->sdio_if_p->D1_gpio, GPIO_FUNC_NULL, false, false, false, false);
gpio_conf(sd_card_p->sdio_if_p->D2_gpio, GPIO_FUNC_NULL, false, false, false, false);
gpio_conf(sd_card_p->sdio_if_p->D3_gpio, GPIO_FUNC_NULL, false, false, false, false);
//TODO: free other resources: PIO, SMs, etc.
sd_unlock(sd_card_p);
}
uint32_t sd_sdio_sectorCount(sd_card_t *sd_card_p) {
myASSERT(!(sd_card_p->state.m_Status & STA_NOINIT));
return CSD_sectors(sd_card_p->state.CSD);
}
static block_dev_err_t sd_sdio_write_blocks(sd_card_t *sd_card_p, const uint8_t *buffer, uint32_t ulSectorNumber,
uint32_t blockCnt) {
TRACE_PRINTF("%s(,,,%zu)\n", __func__, blockCnt);
bool ok = true;
sd_lock(sd_card_p);
if (1 == blockCnt)
ok = sd_sdio_writeSector(sd_card_p, ulSectorNumber, buffer);
else
ok = sd_sdio_writeSectors(sd_card_p, ulSectorNumber, buffer, blockCnt);
sd_unlock(sd_card_p);
if (ok)
return SD_BLOCK_DEVICE_ERROR_NONE;
else
return SD_BLOCK_DEVICE_ERROR_WRITE;
}
static block_dev_err_t sd_sdio_read_blocks(sd_card_t *sd_card_p, uint8_t *buffer, uint32_t ulSectorNumber,
uint32_t ulSectorCount) {
bool ok = true;
sd_lock(sd_card_p);
if (1 == ulSectorCount)
ok = sd_sdio_readSector(sd_card_p, ulSectorNumber, buffer);
else
ok = sd_sdio_readSectors(sd_card_p, ulSectorNumber, buffer, ulSectorCount);
sd_unlock(sd_card_p);
if (ok)
return SD_BLOCK_DEVICE_ERROR_NONE;
else
return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
}
static block_dev_err_t sd_sync(sd_card_t *sd_card_p) {
sd_lock(sd_card_p);
block_dev_err_t err = SD_BLOCK_DEVICE_ERROR_NONE;
if (STATE.ongoing_wr_mlt_blk)
if (!sd_sdio_stopTransmission(sd_card_p, true))
err = SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
sd_unlock(sd_card_p);
return err;
}
void sd_sdio_ctor(sd_card_t *sd_card_p) {
myASSERT(sd_card_p->sdio_if_p); // Must have an interface object
/*
Pins CLK_gpio, D1_gpio, D2_gpio, and D3_gpio are at offsets from pin D0_gpio.
The offsets are determined by sd_driver\SDIO\rp2040_sdio.pio.
*/
myASSERT(!sd_card_p->sdio_if_p->CLK_gpio);
myASSERT(!sd_card_p->sdio_if_p->D1_gpio);
myASSERT(!sd_card_p->sdio_if_p->D2_gpio);
myASSERT(!sd_card_p->sdio_if_p->D3_gpio);
sd_card_p->sdio_if_p->CLK_gpio = (sd_card_p->sdio_if_p->D0_gpio + SDIO_CLK_PIN_D0_OFFSET) % 32;
sd_card_p->sdio_if_p->D1_gpio = sd_card_p->sdio_if_p->D0_gpio + 1;
sd_card_p->sdio_if_p->D2_gpio = sd_card_p->sdio_if_p->D0_gpio + 2;
sd_card_p->sdio_if_p->D3_gpio = sd_card_p->sdio_if_p->D0_gpio + 3;
sd_card_p->state.m_Status = STA_NOINIT;
sd_card_p->init = sd_sdio_init;
sd_card_p->deinit = sd_sdio_deinit;
sd_card_p->write_blocks = sd_sdio_write_blocks;
sd_card_p->read_blocks = sd_sdio_read_blocks;
sd_card_p->sync = sd_sync;
sd_card_p->get_num_sectors = sd_sdio_sectorCount;
sd_card_p->sd_test_com = sd_sdio_test_com;
}
/* spi.c
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
//
#include "hardware/clocks.h"
#include "hardware/structs/clocks.h"
#include "hardware/spi.h"
#include "pico.h"
#include "pico/mutex.h"
#include "pico/platform.h"
#include "pico/stdlib.h"
//
#include "delays.h"
#include "hw_config.h"
#include "my_debug.h"
#include "util.h"
//
#include "my_spi.h"
#ifndef USE_DBG_PRINTF
#pragma GCC diagnostic ignored "-Wunused-variable"
#endif
static bool chk_spi(spi_t *spi_p) {
spi_inst_t *hw_spi = spi_p->hw_inst;
bool ok = true;
if (spi_get_const_hw(hw_spi)->sr & SPI_SSPSR_BSY_BITS) {
DBG_PRINTF("SPI is busy\n");
ok = false;
}
if (spi_get_const_hw(hw_spi)->sr & SPI_SSPSR_RFF_BITS) {
DBG_PRINTF("SPI Receive FIFO full\n");
ok = false;
}
if (spi_get_const_hw(hw_spi)->sr & SPI_SSPSR_RNE_BITS) {
DBG_PRINTF("SPI Receive FIFO not empty\n");
ok = false;
}
if (!(spi_get_const_hw(hw_spi)->sr & SPI_SSPSR_TNF_BITS)) {
DBG_PRINTF("SPI Transmit FIFO is full\n");
ok = false;
}
if (!(spi_get_const_hw(hw_spi)->sr & SPI_SSPSR_TFE_BITS)) {
DBG_PRINTF("SPI Transmit FIFO is not empty\n");
ok = false;
}
return ok;
}
static bool chk_dma(uint chn) {
dma_channel_hw_t *channel = &dma_hw->ch[chn];
bool ok = true;
uint32_t ctrl = channel->ctrl_trig;
if (ctrl & DMA_CH0_CTRL_TRIG_AHB_ERROR_BITS) {
DBG_PRINTF("\tDMA bus error\n");
ok = false;
}
if (ctrl & DMA_CH0_CTRL_TRIG_READ_ERROR_BITS) {
DBG_PRINTF("\tDMA read error\n");
ok = false;
}
if (ctrl & DMA_CH0_CTRL_TRIG_WRITE_ERROR_BITS) {
DBG_PRINTF("\tDMA write error\n");
ok = false;
}
if (ctrl & DMA_CH0_CTRL_TRIG_BUSY_BITS) {
DBG_PRINTF("\tDMA is busy\n");
ok = false;
}
if (!ok) {
dma_debug_channel_hw_t *dbg_ch_p = &dma_debug_hw->ch[chn];
DBG_PRINTF("\tTRANSFER_COUNT: %lu\n", channel->transfer_count);
DBG_PRINTF("\tTRANS_COUNT reload value (DBG_TCR): %lu\n", dbg_ch_p->dbg_tcr);
DBG_PRINTF("\tDREQ counter: %lu\n", dbg_ch_p->dbg_ctdreq);
}
return ok;
}
static bool chk_dmas(spi_t *spi_p) {
bool tx_ok = chk_dma(spi_p->tx_dma);
if (!tx_ok) DBG_PRINTF("TX DMA error\n");
bool rx_ok = chk_dma(spi_p->rx_dma);
if (!rx_ok) DBG_PRINTF("RX DMA error\n");
return tx_ok && rx_ok;
}
/**
* @brief Start a SPI transfer by configuring and starting the DMA channels.
*
* @param spi_p Pointer to the SPI object.
* @param tx Pointer to the transmit buffer. If NULL, data will be filled with SPI_FILL_CHAR.
* @param rx Pointer to the receive buffer. If NULL, data will be ignored.
* @param length Length of the transfer.
*/
void __not_in_flash_func(spi_transfer_start)(spi_t *spi_p, const uint8_t *tx, uint8_t *rx,
size_t length) {
myASSERT(spi_p);
myASSERT(tx || rx);
// tx write increment is already false
if (tx) {
channel_config_set_read_increment(&spi_p->tx_dma_cfg, true);
} else {
static const uint8_t dummy __attribute__((section(".time_critical."))) = SPI_FILL_CHAR;
tx = &dummy;
channel_config_set_read_increment(&spi_p->tx_dma_cfg, false);
}
// rx read increment is already false
if (rx) {
channel_config_set_write_increment(&spi_p->rx_dma_cfg, true);
} else {
static uint8_t dummy = 0xA5;
rx = &dummy;
channel_config_set_write_increment(&spi_p->rx_dma_cfg, false);
}
dma_channel_configure(spi_p->tx_dma, &spi_p->tx_dma_cfg,
&spi_get_hw(spi_p->hw_inst)->dr, // write address
tx, // read address
length, // element count (each element is of
// size transfer_data_size)
false); // start
dma_channel_configure(spi_p->rx_dma, &spi_p->rx_dma_cfg,
rx, // write address
&spi_get_hw(spi_p->hw_inst)->dr, // read address
length, // element count (each element is of
// size transfer_data_size)
false); // start
myASSERT(chk_dmas(spi_p));
myASSERT(chk_spi(spi_p));
// Start the DMA channels:
// start them exactly simultaneously to avoid races (in extreme cases
// the FIFO could overflow)
dma_start_channel_mask((1u << spi_p->tx_dma) | (1u << spi_p->rx_dma));
}
/**
* Calculate the time in milliseconds to transfer the given number of blocks
* over the SPI bus at the given baud rate.
* @param block_count The number of blocks to transfer, each 512 bytes.
* @param spi_p Pointer to the SPI object.
* @return The time in milliseconds to transfer the given number of blocks.
*/
uint32_t calculate_transfer_time_ms(spi_t *spi_p, uint32_t bytes) {
// Calculate the total number of bits to transfer
uint32_t total_bits = bytes * 8;
// Get the baud rate from the SPI interface
uint32_t baud_rate = spi_get_baudrate(spi_p->hw_inst);
// Calculate the time to transfer all bits in seconds
float transfer_time_sec = (double)total_bits / baud_rate;
// Convert the time to milliseconds
float transfer_time_ms = transfer_time_sec * 1000;
transfer_time_ms *= 1.5f; // Add 50% for overhead
transfer_time_ms += 4.0f; // For fixed overhead
return (uint32_t)transfer_time_ms;
}
/**
* @brief Wait until SPI transfer is complete.
* @details This function waits until the SPI master completes the transfer
* or a timeout has occurred. The timeout is specified in milliseconds.
* If the timeout is reached the function will return false.
* This function uses busy waiting to check for completion of the transfer.
*
* @param spi_p The SPI configuration.
* @param timeout_ms The timeout in milliseconds.
* @return true if the transfer is complete, false if the timeout is reached.
*/
bool __not_in_flash_func(spi_transfer_wait_complete)(spi_t *spi_p, uint32_t timeout_ms) {
myASSERT(spi_p);
bool timed_out = false;
// Record the start time in milliseconds
uint32_t start = millis();
// Wait until DMA channels are not busy or timeout is reached
while ((dma_channel_is_busy(spi_p->rx_dma) || dma_channel_is_busy(spi_p->tx_dma)) &&
millis() - start < timeout_ms)
tight_loop_contents();
// Check if the DMA channels are still busy
timed_out = dma_channel_is_busy(spi_p->rx_dma) || dma_channel_is_busy(spi_p->tx_dma);
// Print debug information if the DMA channels are still busy
if (timed_out) {
DBG_PRINTF("DMA busy wait timed out in %s\n", __FUNCTION__);
} else {
// If the DMA channels are not busy, wait for the SPI peripheral to become idle
start = millis();
while (spi_is_busy(spi_p->hw_inst) && millis() - start < timeout_ms)
tight_loop_contents();
// Check if the SPI peripheral is still busy
timed_out = spi_is_busy(spi_p->hw_inst);
// Print debug information if the SPI peripheral is still busy
if (timed_out) {
DBG_PRINTF("SPI busy wait timed out in %s\n", __FUNCTION__);
}
}
// Check the status of the SPI peripheral
bool spi_ok = chk_spi(spi_p);
if (timed_out || !spi_ok) {
chk_dmas(spi_p);
DBG_PRINTF("DMA_INTR: 0b%s\n", uint_binary_str(dma_hw->intr));
DBG_PRINTF("TX DMA CTRL_TRIG: 0b%s\n",
uint_binary_str(dma_hw->ch[spi_p->tx_dma].ctrl_trig));
DBG_PRINTF("RX DMA CTRL_TRIG: 0b%s\n",
uint_binary_str(dma_hw->ch[spi_p->rx_dma].ctrl_trig));
DBG_PRINTF("SPI SSPCR0: 0b%s\n", uint_binary_str(spi_get_hw(spi_p->hw_inst)->cr0));
DBG_PRINTF("SPI SSPCR1: 0b%s\n", uint_binary_str(spi_get_hw(spi_p->hw_inst)->cr1));
DBG_PRINTF("SPI_SSPSR: 0b%s\n", uint_binary_str(spi_get_const_hw(spi_p->hw_inst)->sr));
DBG_PRINTF("SPI_SSPDMACR: 0b%s\n",
uint_binary_str(spi_get_const_hw(spi_p->hw_inst)->dmacr));
dma_channel_abort(spi_p->rx_dma);
dma_channel_abort(spi_p->tx_dma);
}
// Return true if the transfer is complete and the SPI peripheral is in a good state
return !(timed_out || !spi_ok);
}
/**
* SPI Transfer: Read & Write (simultaneously) on SPI bus
* @param spi_p Pointer to the SPI object.
* @param tx Pointer to the transmit buffer. If NULL, SPI_FILL_CHAR is sent as each data
* element.
* @param rx Pointer to the receive buffer. If NULL, data is ignored.
* @param length Number of data elements to transfer.
* @return true if the transfer is completed successfully within the timeout.
* @return false if the transfer times out or encounters an error.
*/
bool __not_in_flash_func(spi_transfer)(spi_t *spi_p, const uint8_t *tx, uint8_t *rx,
size_t length) {
spi_transfer_start(spi_p, tx, rx, length);
// Related to timeouts in spi_lock and sd_lock
uint32_t timeout = calculate_transfer_time_ms(spi_p, length);
return spi_transfer_wait_complete(spi_p, timeout);
}
/**
* @brief Initialize the SPI peripheral and DMA channels.
*
* @param spi_p Pointer to the SPI object.
* @return true if the initialization is successful, false otherwise.
*/
bool my_spi_init(spi_t *spi_p) {
auto_init_mutex(my_spi_init_mutex);
mutex_enter_blocking(&my_spi_init_mutex);
if (!spi_p->initialized) {
//// The SPI may be shared (using multiple SSs); protect it
if (!mutex_is_initialized(&spi_p->mutex)) mutex_init(&spi_p->mutex);
spi_lock(spi_p);
// Defaults:
if (!spi_p->hw_inst) spi_p->hw_inst = spi0;
if (!spi_p->baud_rate) spi_p->baud_rate = clock_get_hz(clk_sys) / 12;
/* Configure component */
// Enable SPI at 100 kHz and connect to GPIOs
spi_init(spi_p->hw_inst, 100 * 1000);
myASSERT(spi_p->spi_mode < 4);
switch (spi_p->spi_mode) {
case 0:
spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
break;
case 1:
spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_0, SPI_CPHA_1, SPI_MSB_FIRST);
break;
case 2:
spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_1, SPI_CPHA_0, SPI_MSB_FIRST);
break;
case 3:
spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_1, SPI_CPHA_1, SPI_MSB_FIRST);
break;
default:
spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
break;
}
gpio_set_function(spi_p->miso_gpio, GPIO_FUNC_SPI);
gpio_set_function(spi_p->mosi_gpio, GPIO_FUNC_SPI);
gpio_set_function(spi_p->sck_gpio, GPIO_FUNC_SPI);
// ss_gpio is initialized in sd_spi_ctor()
// Slew rate limiting levels for GPIO outputs.
// enum gpio_slew_rate { GPIO_SLEW_RATE_SLOW = 0, GPIO_SLEW_RATE_FAST = 1 }
// void gpio_set_slew_rate (uint gpio,enum gpio_slew_rate slew)
// Default appears to be GPIO_SLEW_RATE_SLOW.
gpio_set_slew_rate(spi_p->sck_gpio, GPIO_SLEW_RATE_FAST);
/* Drive strength levels for GPIO outputs:
enum gpio_drive_strength {
GPIO_DRIVE_STRENGTH_2MA = 0,
GPIO_DRIVE_STRENGTH_4MA = 1,
GPIO_DRIVE_STRENGTH_8MA = 2,
GPIO_DRIVE_STRENGTH_12MA = 3 }
enum gpio_drive_strength gpio_get_drive_strength (uint gpio)
*/
if (spi_p->set_drive_strength) {
gpio_set_drive_strength(spi_p->mosi_gpio, spi_p->mosi_gpio_drive_strength);
gpio_set_drive_strength(spi_p->sck_gpio, spi_p->sck_gpio_drive_strength);
}
// SD cards' DO MUST be pulled up. However, it might be done externally.
if (!spi_p->no_miso_gpio_pull_up) gpio_pull_up(spi_p->miso_gpio);
// gpio_set_input_hysteresis_enabled(spi_p->miso_gpio, false);
// Check if the user has provided DMA channels
if (spi_p->use_static_dma_channels) {
// Claim the channels provided
dma_channel_claim(spi_p->tx_dma);
dma_channel_claim(spi_p->rx_dma);
} else {
// Grab some unused dma channels
spi_p->tx_dma = dma_claim_unused_channel(true);
spi_p->rx_dma = dma_claim_unused_channel(true);
}
spi_p->tx_dma_cfg = dma_channel_get_default_config(spi_p->tx_dma);
spi_p->rx_dma_cfg = dma_channel_get_default_config(spi_p->rx_dma);
channel_config_set_transfer_data_size(&spi_p->tx_dma_cfg, DMA_SIZE_8);
channel_config_set_transfer_data_size(&spi_p->rx_dma_cfg, DMA_SIZE_8);
// We set the outbound DMA to transfer from a memory buffer to the SPI
// transmit FIFO paced by the SPI TX FIFO DREQ The default is for the
// read address to increment every element (in this case 1 byte -
// DMA_SIZE_8) and for the write address to remain unchanged.
channel_config_set_dreq(&spi_p->tx_dma_cfg, spi_get_dreq(spi_p->hw_inst, true));
channel_config_set_write_increment(&spi_p->tx_dma_cfg, false);
// We set the inbound DMA to transfer from the SPI receive FIFO to a
// memory buffer paced by the SPI RX FIFO DREQ We configure the read
// address to remain unchanged for each element, but the write address
// to increment (so data is written throughout the buffer)
channel_config_set_dreq(&spi_p->rx_dma_cfg, spi_get_dreq(spi_p->hw_inst, false));
channel_config_set_read_increment(&spi_p->rx_dma_cfg, false);
LED_INIT();
spi_p->initialized = true;
spi_unlock(spi_p);
}
mutex_exit(&my_spi_init_mutex);
return true;
}
/* [] END OF FILE */
/* spi.h
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
//
// Pico includes
#include "pico/stdlib.h"
#include "pico/mutex.h"
#include "pico/types.h"
//
#include "hardware/dma.h"
#include "hardware/gpio.h"
#include "hardware/irq.h"
#include "hardware/spi.h"
//
#include "my_debug.h"
#include "sd_timeouts.h"
#ifdef __cplusplus
extern "C" {
#endif
#define SPI_FILL_CHAR (0xFF)
// "Class" representing SPIs
typedef struct spi_t {
spi_inst_t *hw_inst; // SPI HW
uint miso_gpio; // SPI MISO GPIO number (not pin number)
uint mosi_gpio;
uint sck_gpio;
uint baud_rate;
/* The different modes of the Motorola SPI protocol are:
- Mode 0: When CPOL and CPHA are both 0, data sampled at the leading rising edge of the
clock pulse and shifted out on the falling edge. This is the most common mode for SPI bus
communication.
- Mode 1: When CPOL is 0 and CPHA is 1, data sampled at the trailing falling edge and
shifted out on the rising edge.
- Mode 2: When CPOL is 1 and CPHA is 0, data sampled at the leading falling edge
and shifted out on the rising edge.
- Mode 3: When CPOL is 1 and CPHA is 1, data sampled at the trailing rising edge and
shifted out on the falling edge. */
uint spi_mode;
bool no_miso_gpio_pull_up;
/* Drive strength levels for GPIO outputs:
GPIO_DRIVE_STRENGTH_2MA,
GPIO_DRIVE_STRENGTH_4MA,
GPIO_DRIVE_STRENGTH_8MA,
GPIO_DRIVE_STRENGTH_12MA */
bool set_drive_strength;
enum gpio_drive_strength mosi_gpio_drive_strength;
enum gpio_drive_strength sck_gpio_drive_strength;
bool use_static_dma_channels;
uint tx_dma;
uint rx_dma;
/* The following fields are not part of the configuration. They are dynamically assigned. */
dma_channel_config tx_dma_cfg;
dma_channel_config rx_dma_cfg;
mutex_t mutex;
bool initialized;
} spi_t;
void spi_transfer_start(spi_t *spi_p, const uint8_t *tx, uint8_t *rx, size_t length);
uint32_t calculate_transfer_time_ms(spi_t *spi_p, uint32_t bytes);
bool spi_transfer_wait_complete(spi_t *spi_p, uint32_t timeout_ms);
bool spi_transfer(spi_t *spi_p, const uint8_t *tx, uint8_t *rx, size_t length);
bool my_spi_init(spi_t *spi_p);
static inline void spi_lock(spi_t *spi_p) {
myASSERT(mutex_is_initialized(&spi_p->mutex));
mutex_enter_blocking(&spi_p->mutex);
}
static inline void spi_unlock(spi_t *spi_p) {
myASSERT(mutex_is_initialized(&spi_p->mutex));
mutex_exit(&spi_p->mutex);
}
/*
This uses the Pico LED to show SD card activity.
You can use it to get a rough idea of utilization.
Warning: Pico W uses GPIO 25 for SPI communication to the CYW43439.
You can enable this by putting something like
add_compile_definitions(USE_LED=1)
in CMakeLists.txt, for example.
*/
#if !defined(NO_PICO_LED) && defined(USE_LED) && USE_LED && defined(PICO_DEFAULT_LED_PIN)
# define LED_INIT() \
{ \
gpio_init(PICO_DEFAULT_LED_PIN); \
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); \
}
# define LED_ON() gpio_put(PICO_DEFAULT_LED_PIN, 1)
# define LED_OFF() gpio_put(PICO_DEFAULT_LED_PIN, 0)
#else
# define LED_ON()
# define LED_OFF()
# define LED_INIT()
#endif
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */
/**
* @file sd_card_spi.c
* @brief SD Card SPI Driver
*
* @section License
*
* Copyright 2021 Carl John Kugler III
*
* Licensed under the Apache License, Version 2.0 (the License); you may not use
* this file except in compliance with the License. You may obtain a copy of the
* License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
/*
* This code borrows heavily from the Mbed SDBlockDevice:
* https://os.mbed.com/docs/mbed-os/v5.15/apis/sdblockdevice.html
* mbed-os/components/storage/blockdevice/COMPONENT_SD/SDBlockDevice.cpp
*
* Editor: Carl Kugler (carlk3@gmail.com)
*
* Remember your ABCs: "Always Be Cobbling!"
*/
/* mbed Microcontroller Library
* Copyright (c) 2006-2013 ARM Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Introduction
* ------------
* SD and MMC cards support a number of interfaces, but common to them all
* is one based on SPI. Since we already have the mbed SPI Interface, it will
* be used for SD cards.
*
* The main reference I'm using is Chapter 7, "SPI Mode" of:
* http://www.sdcard.org/developers/tech/sdcard/pls/Simplified_Physical_Layer_Spec.pdf
*
* SPI Startup
* -----------
* The SD card powers up in SD mode. The start-up procedure is complicated
* by the requirement to support older SDCards in a backwards compatible
* way with the new higher capacity variants SDHC and SDHC.
*
* The following figures from the specification with associated text describe
* the SPI mode initialisation process:
* - Figure 7-1: SD Memory Card State Diagram (SPI mode)
* - Figure 7-2: SPI Mode Initialization Flow
*
* Firstly, a low initial clock should be selected (in the range of 100-
* 400kHZ). After initialisation has been completed, the switch to a
* higher clock speed can be made (e.g. 1MHz). Newer cards will support
* higher speeds than the default _transfer_sck defined here.
*
* Next, note the following from the SDCard specification (note to
* Figure 7-1):
*
* In any of the cases CMD1 is not recommended because it may be difficult for
* the host to distinguish between MultiMediaCard and SD Memory Card
*
* Hence CMD1 is not used for the initialisation sequence.
*
* The SPI interface mode is selected by asserting CS low and sending the
* reset command (CMD0). The card will respond with a (R1) response.
* In practice many cards initially respond with 0xff or invalid data
* which is ignored. Data is read until a valid response is received
* or the number of re-reads has exceeded a maximim count. If a valid
* response is not received then the CMD0 can be retried. This
* has been found to successfully initialise cards where the SPI master
* (on MCU) has been reset but the SDCard has not, so the first
* CMD0 may be lost.
*
* CMD8 is optionally sent to determine the voltage range supported, and
* indirectly determine whether it is a version 1.x SD/non-SD card or
* version 2.x. I'll just ignore this for now.
*
* ACMD41 is repeatedly issued to initialise the card, until "in idle"
* (bit 0) of the R1 response goes to '0', indicating it is initialised.
*
* You should also indicate whether the host supports High Capicity cards,
* and check whether the card is high capacity - i'll also ignore this.
*
* SPI Protocol
* ------------
* The SD SPI protocol is based on transactions made up of 8-bit words, with
* the host starting every bus transaction by asserting the CS signal low. The
* card always responds to commands, data blocks and errors.
*
* The protocol supports a CRC, but by default it is off (except for the
* first reset CMD0, where the CRC can just be pre-calculated, and CMD8)
* I'll leave the CRC off I think!
*
* Standard capacity cards have variable data block sizes, whereas High
* Capacity cards fix the size of data block to 512 bytes. I'll therefore
* just always use the Standard Capacity cards with a block size of 512 bytes.
* This is set with CMD16.
*
* You can read and write single blocks (CMD17, CMD25) or multiple blocks
* (CMD18, CMD25). For simplicity, I'll just use single block accesses. When
* the card gets a read command, it responds with a response token, and then
* a data token or an error.
*
* SPI Command Format
* ------------------
* Commands are 6-bytes long, containing the command, 32-bit argument, and CRC.
*
* +---------------+------------+------------+-----------+----------+--------------+
* | 01 | cmd[5:0] | arg[31:24] | arg[23:16] | arg[15:8] | arg[7:0] | crc[6:0] |
* 1 |
* +---------------+------------+------------+-----------+----------+--------------+
*
* As I'm not using CRC, I can fix that byte to what is needed for CMD0 (0x95)
*
* All Application Specific commands shall be preceded with APP_CMD (CMD55).
*
* SPI Response Format
* -------------------
* The main response format (R1) is a status byte (normally zero). Key flags:
* idle - 1 if the card is in an idle state/initialising
* cmd - 1 if an illegal command code was detected
*
* +-------------------------------------------------+
* R1 | 0 | arg | addr | seq | crc | cmd | erase | idle |
* +-------------------------------------------------+
*
* R1b is the same, except it is followed by a busy signal (zeros) until
* the first non-zero byte when it is ready again.
*
* Data Response Token
* -------------------
* Every data block written to the card is acknowledged by a byte
* response token
*
* +----------------------+
* | xxx | 0 | status | 1 |
* +----------------------+
* 010 - OK!
* 101 - CRC Error
* 110 - Write Error
*
* Single Block Read and Write
* ---------------------------
*
* Block transfers have a byte header, followed by the data, followed
* by a 16-bit CRC. In our case, the data will always be 512 bytes.
*
* +------+---------+---------+- - - -+---------+-----------+----------+
* | 0xFE | data[0] | data[1] | | data[n] | crc[15:8] | crc[7:0] |
* +------+---------+---------+- - - -+---------+-----------+----------+
*/
/* Standard includes. */
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
//
#include "crc.h"
#include "diskio.h" /* Declarations of disk functions */ // Needed for STA_NOINIT, ...
#include "hw_config.h" // Hardware Configuration of the SPI and SD Card "objects"
#include "my_debug.h"
#include "delays.h"
#include "sd_card.h"
#include "sd_card_constants.h"
#include "sd_spi.h"
#include "sd_timeouts.h"
#include "util.h"
//
#include "sd_card_spi.h"
#if defined(NDEBUG) || !USE_DBG_PRINTF
# pragma GCC diagnostic ignored "-Wunused-function"
# pragma GCC diagnostic ignored "-Wunused-variable"
# pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
#ifndef TRACE
# define TRACE 0
#endif
#ifndef SD_CRC_ENABLED
#define SD_CRC_ENABLED 1
#endif
#if SD_CRC_ENABLED
static bool crc_on = true;
#else
static bool crc_on = false;
#endif
#define TRACE_PRINTF(fmt, args...)
//#define TRACE_PRINTF DBG_PRINTF
/**
* @brief Control Tokens
*/
typedef enum {
SPI_DATA_RESPONSE_MASK = 0x1F,
SPI_DATA_ACCEPTED = 0x05,
SPI_DATA_CRC_ERROR = 0x0B,
SPI_DATA_WRITE_ERROR = 0x0D,
SPI_START_BLOCK = 0xFE,
SPI_START_BLK_MUL_WRITE = 0xFC,
SPI_STOP_TRAN = 0xFD,
} spi_control_t;
/**
* @brief Data Error Token Mask
*/
typedef enum {
SPI_DATA_READ_ERROR_MASK = 0xF,
} spi_data_read_error_mask_t;
/**
* @brief Data Error Token Flags
*/
typedef enum {
SPI_READ_ERROR = 0x1 << 0,
SPI_READ_ERROR_CC = 0x1 << 1,
SPI_READ_ERROR_ECC_C = 0x1 << 2,
SPI_READ_ERROR_OFR = 0x1 << 3,
} spi_data_read_error_t;
/**
* @brief R1 Response Format
*/
typedef enum {
R1_NO_RESPONSE = 0xFF,
R1_RESPONSE_RECV = 0x80,
R1_IDLE_STATE = 1 << 0,
R1_ERASE_RESET = 1 << 1,
R1_ILLEGAL_COMMAND = 1 << 2,
R1_COM_CRC_ERROR = 1 << 3,
R1_ERASE_SEQUENCE_ERROR = 1 << 4,
R1_ADDRESS_ERROR = 1 << 5,
R1_PARAMETER_ERROR = 1 << 6,
} spi_r1_response_t;
/* SIZE in Bytes */
#define PACKET_SIZE 6 /*!< SD Packet size CMD+ARG+CRC */
/**
* @brief OCR Register Flags
*/
typedef enum {
OCR_HCS_CCS = 0x1 << 30,
OCR_LOW_VOLTAGE = 0x01 << 24,
OCR_3_3V = 0x1 << 20,
} spi_ocr_register_t;
/**
* @brief Control Command
*
* @param x Command
*/
#define SPI_CMD(x) (0x40 | (x & 0x3f))
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
/**
* @brief Send a command over SPI interface
*
* @param sd_card_p Pointer to the sd card structure
* @param cmd Command to send
* @param arg Argument to send with the command
*
* @return The response from the card
*
* @details
* This function sends a command over the SPI interface and waits for the
* response. The command is prepared by setting the correct bits in the packet
* and calculating the CRC if necessary. The command is then sent and the
* response is received and returned.
*
* @note
* For CMD12_STOP_TRANSMISSION, the first byte received is a stuff byte and
* should be discarded.
*/
static uint8_t sd_cmd_spi(sd_card_t *sd_card_p, cmdSupported cmd, uint32_t arg) {
uint8_t cmd_packet[PACKET_SIZE] = {
SPI_CMD(cmd),
(arg >> 24),
(arg >> 16),
(arg >> 8),
(arg >> 0),
};
if (crc_on) {
cmd_packet[5] = (crc7(cmd_packet, 5) << 1) | 0x01;
} else {
// CMD0 is executed in SD mode, hence should have correct CRC
// CMD8 CRC verification is always enabled
switch (cmd) {
case CMD0_GO_IDLE_STATE:
cmd_packet[5] = 0x95;
break;
case CMD8_SEND_IF_COND:
cmd_packet[5] = 0x87;
break;
default:
// Make sure bit 0-End bit is high
cmd_packet[5] = 0xFF;
break;
}
}
// send a command
for (size_t i = 0; i < PACKET_SIZE; i++) {
sd_spi_write(sd_card_p, cmd_packet[i]);
}
// The received byte immediately following CMD12 is a stuff byte,
// it should be discarded before receive the response of the CMD12.
if (CMD12_STOP_TRANSMISSION == cmd) {
sd_spi_write(sd_card_p, SPI_FILL_CHAR);
}
// Loop for response: Response is sent back within command response time
// (NCR), 0 to 8 bytes for SDC
uint8_t response;
for (size_t i = 0; i < 0x10; i++) {
response = sd_spi_read(sd_card_p);
// Got the response
if (!(response & R1_RESPONSE_RECV)) {
break;
}
}
return response;
}
#pragma GCC diagnostic pop
/**
* @brief Wait for the SD card to be ready for the next command.
*
* Sends dummy clocks with DI held high until the card releases the DO line.
*
* @param sd_card_p Pointer to the sd_card_t struct.
* @param timeout The maximum time to wait for the card to become ready.
*
* @return true if the card is ready, false otherwise.
*/
static bool sd_wait_ready(sd_card_t *sd_card_p, uint32_t timeout) {
char resp;
// Keep sending dummy clocks with DI held high until the card releases the
// DO line
uint32_t start = millis();
do {
resp = sd_spi_write_read(sd_card_p, 0xFF);
} while (resp != 0xFF && millis() - start < timeout);
/* Checking for 0xFF provides a little extra margin to
make sure that DO has gone high and stayed there.
(the alternative is to accept the first non-zero byte) */
if (resp != 0xFF) DBG_PRINTF("%s failed\n", __FUNCTION__);
// Return success/failure
return (0xFF == resp);
}
/* Locks the SD card and acquires its SPI
Potential optimization: the SPI could be locked separately,
so if there are multiple SD cards on one SPI,
another SD card could use the SPI during any gaps
in the first SD card's utilization.
However, these gaps are generally small.
*/
static void sd_acquire(sd_card_t *sd_card_p) {
sd_lock(sd_card_p);
sd_spi_acquire(sd_card_p);
}
static void sd_release(sd_card_t *sd_card_p) {
sd_spi_release(sd_card_p);
sd_unlock(sd_card_p);
}
#if TRACE
static const char *cmd2str(const cmdSupported cmd) {
switch (cmd) {
default:
return "CMD_NOT_SUPPORTED";
case CMD0_GO_IDLE_STATE:
return "CMD0_GO_IDLE_STATE";
case CMD1_SEND_OP_COND:
return "CMD1_SEND_OP_COND";
case CMD6_SWITCH_FUNC:
return "CMD6_SWITCH_FUNC";
case CMD8_SEND_IF_COND:
return "CMD8_SEND_IF_COND";
case CMD9_SEND_CSD:
return "CMD9_SEND_CSD";
case CMD10_SEND_CID:
return "CMD10_SEND_CID";
case CMD12_STOP_TRANSMISSION:
return "CMD12_STOP_TRANSMISSION";
case CMD13_SEND_STATUS:
return "CMD13_SEND_STATUS or ACMD6_SET_BUS_WIDTH or "
"ACMD13_SD_STATUS";
case CMD16_SET_BLOCKLEN:
return "CMD16_SET_BLOCKLEN";
case CMD17_READ_SINGLE_BLOCK:
return "CMD17_READ_SINGLE_BLOCK";
case CMD18_READ_MULTIPLE_BLOCK:
return "CMD18_READ_MULTIPLE_BLOCK";
case CMD24_WRITE_BLOCK:
return "CMD24_WRITE_BLOCK";
case CMD25_WRITE_MULTIPLE_BLOCK:
return "CMD25_WRITE_MULTIPLE_BLOCK";
case CMD27_PROGRAM_CSD:
return "CMD27_PROGRAM_CSD";
case CMD32_ERASE_WR_BLK_START_ADDR:
return "CMD32_ERASE_WR_BLK_START_ADDR";
case CMD33_ERASE_WR_BLK_END_ADDR:
return "CMD33_ERASE_WR_BLK_END_ADDR";
case CMD38_ERASE:
return "CMD38_ERASE";
case CMD55_APP_CMD:
return "CMD55_APP_CMD";
case CMD56_GEN_CMD:
return "CMD56_GEN_CMD";
case CMD58_READ_OCR:
return "CMD58_READ_OCR";
case CMD59_CRC_ON_OFF:
return "CMD59_CRC_ON_OFF";
// case ACMD6_SET_BUS_WIDTH:
// case ACMD13_SD_STATUS:
case ACMD22_SEND_NUM_WR_BLOCKS:
return "ACMD22_SEND_NUM_WR_BLOCKS";
case ACMD23_SET_WR_BLK_ERASE_COUNT:
return "ACMD23_SET_WR_BLK_ERASE_COUNT";
case ACMD41_SD_SEND_OP_COND:
return "ACMD41_SD_SEND_OP_COND";
case ACMD42_SET_CLR_CARD_DETECT:
return "ACMD42_SET_CLR_CARD_DETECT";
case ACMD51_SEND_SCR:
return "ACMD51_SEND_SCR";
}
}
#endif
/**
* @brief Check the response of the card status command (CMD13) and set
* the status bit accordingly.
*
* @param response The response of the card status command (CMD13).
*
* @return The status of the SD card.
*/
static int chk_CMD13_response(uint32_t response) {
int32_t status = 0;
DBG_PRINTF("Card Status: R2: 0x%" PRIx32 "\n", response);
if (response & 0x01 << 0) {
DBG_PRINTF("Card is Locked\n");
status |= SD_BLOCK_DEVICE_ERROR_WRITE;
}
if (response & 0x01 << 1) {
DBG_PRINTF("WP Erase Skip, Lock/Unlock Cmd Failed\n");
status |= SD_BLOCK_DEVICE_ERROR_WRITE_PROTECTED;
}
if (response & 0x01 << 2) {
DBG_PRINTF("Error\n");
status |= SD_BLOCK_DEVICE_ERROR_WRITE;
}
if (response & 0x01 << 3) {
DBG_PRINTF("CC Error\n");
status |= SD_BLOCK_DEVICE_ERROR_WRITE;
}
if (response & 0x01 << 4) {
DBG_PRINTF("Card ECC Failed\n");
status |= SD_BLOCK_DEVICE_ERROR_WRITE;
}
if (response & 0x01 << 5) {
DBG_PRINTF("WP Violation\n");
status |= SD_BLOCK_DEVICE_ERROR_WRITE_PROTECTED;
}
if (response & 0x01 << 6) {
DBG_PRINTF("Erase Param\n");
status |= SD_BLOCK_DEVICE_ERROR_ERASE;
}
if (response & 0x01 << 7) {
DBG_PRINTF("Out of Range, CSD_Overwrite\n");
status |= SD_BLOCK_DEVICE_ERROR_PARAMETER;
}
if (response & 0x01 << 8) {
DBG_PRINTF("In Idle State\n");
status |= SD_BLOCK_DEVICE_ERROR_NONE;
}
if (response & 0x01 << 9) {
DBG_PRINTF("Erase Reset\n");
status |= SD_BLOCK_DEVICE_ERROR_ERASE;
}
if (response & 0x01 << 10) {
DBG_PRINTF("Illegal Command\n");
status |= SD_BLOCK_DEVICE_ERROR_UNSUPPORTED;
}
if (response & 0x01 << 11) {
DBG_PRINTF("Com CRC Error\n");
status |= SD_BLOCK_DEVICE_ERROR_CRC;
}
if (response & 0x01 << 12) {
DBG_PRINTF("Erase Sequence Error\n");
status |= SD_BLOCK_DEVICE_ERROR_ERASE;
}
if (response & 0x01 << 13) {
DBG_PRINTF("Address Error\n");
status |= SD_BLOCK_DEVICE_ERROR_PARAMETER;
}
if (response & 0x01 << 14) {
DBG_PRINTF("Parameter Error\n");
status |= SD_BLOCK_DEVICE_ERROR_PARAMETER;
}
return status;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
/**
* @brief Send a command to the SD card.
* @param sd_card_p pointer to sd_card_t structure
* @param cmd command to send
* @param arg argument for the command
* @param isAcmd true if this is an application command
* @param resp pointer to a uint32_t to save the response
* @return error code
*
* This function sends a command to the SD card and waits for the response.
* It will retry the command up to sd_timeouts.sd_command_retries times if there is no response.
* The response is stored in the @p resp variable if it is not NULL.
* The function will return SD_BLOCK_DEVICE_ERROR_NONE if the command was successful,
* SD_BLOCK_DEVICE_ERROR_NO_RESPONSE if there was no response,
* SD_BLOCK_DEVICE_ERROR_CRC if there was a CRC error,
* SD_BLOCK_DEVICE_ERROR_UNSUPPORTED if the command was not supported,
* SD_BLOCK_DEVICE_ERROR_PARAMETER if there was a parameter error,
* SD_BLOCK_DEVICE_ERROR_ERASE if there was an erase error.
*/
static block_dev_err_t sd_cmd(sd_card_t *sd_card_p, const cmdSupported cmd, uint32_t arg,
bool isAcmd, uint32_t *resp) {
// TRACE_PRINTF("%s(%s(0x%08lx)): ", __FUNCTION__, cmd2str(cmd), arg);
myASSERT(sd_is_locked(sd_card_p));
myASSERT(0 == gpio_get(sd_card_p->spi_if_p->ss_gpio));
int32_t status = SD_BLOCK_DEVICE_ERROR_NONE;
uint32_t response = 0;
// No need to wait for card to be ready when sending the stop command
if (CMD12_STOP_TRANSMISSION != cmd && CMD0_GO_IDLE_STATE != cmd) {
if (false == sd_wait_ready(sd_card_p, sd_timeouts.sd_command)) {
DBG_PRINTF("Card not ready yet\n");
return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
}
}
for (unsigned i = 0; i < sd_timeouts.sd_command_retries; i++) {
// Send CMD55 for APP command first
if (isAcmd) {
response = sd_cmd_spi(sd_card_p, CMD55_APP_CMD, 0x0);
// Wait for card to be ready after CMD55
if (false == sd_wait_ready(sd_card_p, sd_timeouts.sd_command)) {
DBG_PRINTF("Card not ready yet\n");
}
}
// Send command over SPI interface
response = sd_cmd_spi(sd_card_p, cmd, arg);
if (R1_NO_RESPONSE == response) {
DBG_PRINTF("No response CMD:%d\n", cmd);
// Re-try command
continue;
}
break;
}
// Pass the response to the command call if required
if (NULL != resp) {
*resp = response;
}
// Process the response R1 : Exit on CRC/Illegal command error/No response
if (R1_NO_RESPONSE == response) {
DBG_PRINTF("No response CMD:%d response: 0x%" PRIx32 "\n", cmd, response);
return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
}
if (response & R1_COM_CRC_ERROR && ACMD23_SET_WR_BLK_ERASE_COUNT != cmd) {
DBG_PRINTF("CRC error CMD:%d response 0x%" PRIx32 "\n", cmd, response);
return SD_BLOCK_DEVICE_ERROR_CRC; // CRC error
}
if (response & R1_ILLEGAL_COMMAND) {
if (ACMD23_SET_WR_BLK_ERASE_COUNT != cmd)
DBG_PRINTF("Illegal command CMD:%d response 0x%" PRIx32 "\n", cmd, response);
if (CMD8_SEND_IF_COND == cmd) {
// Illegal command is for Ver1 or not SD Card
sd_card_p->state.card_type = CARD_UNKNOWN;
}
return SD_BLOCK_DEVICE_ERROR_UNSUPPORTED; // Command not supported
}
// DBG_PRINTF("CMD:%d \t arg:0x%" PRIx32 " \t Response:0x%" PRIx32 "\n",
// cmd, arg, response);
// Set status for other errors
if ((response & R1_ERASE_RESET) || (response & R1_ERASE_SEQUENCE_ERROR)) {
status = SD_BLOCK_DEVICE_ERROR_ERASE; // Erase error
} else if ((response & R1_ADDRESS_ERROR) || (response & R1_PARAMETER_ERROR)) {
// Misaligned address / invalid address block length
status = SD_BLOCK_DEVICE_ERROR_PARAMETER;
}
// Get rest of the response part for other commands
switch (cmd) {
case CMD8_SEND_IF_COND: // Response R7
DBG_PRINTF("V2-Version Card\n");
sd_card_p->state.card_type = SDCARD_V2; // fallthrough
// Note: No break here, need to read rest of the response
case CMD58_READ_OCR: // Response R3
response = (sd_spi_read(sd_card_p) << 24);
response |= (sd_spi_read(sd_card_p) << 16);
response |= (sd_spi_read(sd_card_p) << 8);
response |= sd_spi_read(sd_card_p);
DBG_PRINTF("R3/R7: 0x%" PRIx32 "\n", response);
break;
case CMD12_STOP_TRANSMISSION: // Response R1b
case CMD38_ERASE:
sd_wait_ready(sd_card_p, sd_timeouts.sd_command);
break;
case CMD13_SEND_STATUS: // Response R2
response <<= 8;
response |= sd_spi_read(sd_card_p);
if (response) status = chk_CMD13_response(response);
default:;
}
// Pass the updated response to the command
if (NULL != resp) {
*resp = response;
}
return status;
}
#pragma GCC diagnostic pop
/* R7 response pattern for CMD8 */
#define CMD8_PATTERN (0xAA)
/**
* @brief Send CMD8 to check if the card supports version 2.0 of the SD spec.
*
* @param sd_card_p Pointer to the SD card information structure.
* @return sd_block_dev_err_t Returns SD_BLOCK_DEVICE_ERROR_NONE if the card
* supports version 2.0 of the SD spec, SD_BLOCK_DEVICE_ERROR_UNSUPPORTED if the
* card does not support version 2.0 of the SD spec, or other error codes as
* defined in sd_block_dev_err_t.
*
* @details CMD8 is sent to check if the card supports version 2.0 of the SD
* spec. The response from the card is checked to see if the card supports
* version 2.0 of the SD spec. If the card does not support version 2.0 of the SD
* spec, the card type is set to CARD_UNKNOWN and the card is considered
* unreadable.
*/
static block_dev_err_t sd_cmd8(sd_card_t *sd_card_p) {
uint32_t arg = (CMD8_PATTERN << 0); // [7:0]check pattern
uint32_t response = 0;
int32_t status = SD_BLOCK_DEVICE_ERROR_NONE;
arg |= (0x1 << 8); // 2.7-3.6V // [11:8]supply voltage(VHS)
status = sd_cmd(sd_card_p, CMD8_SEND_IF_COND, arg, false, &response);
// Verify voltage and pattern for V2 version of card
if ((SD_BLOCK_DEVICE_ERROR_NONE == status) && (SDCARD_V2 == sd_card_p->state.card_type)) {
// If check pattern is not matched, CMD8 communication is not valid
if ((response & 0xFFF) != arg) {
DBG_PRINTF("CMD8 Pattern mismatch 0x%" PRIx32 " : 0x%" PRIx32 "\n", arg, response);
sd_card_p->state.card_type = CARD_UNKNOWN;
status = SD_BLOCK_DEVICE_ERROR_UNUSABLE;
}
}
return status;
}
static block_dev_err_t read_bytes(sd_card_t *sd_card_p, uint8_t *buffer, uint32_t length);
/**
* @brief Get the number of sectors on an SD card.
*
* @param sd_card_p A pointer to the sd_card_t structure for the card.
*
* @return The number of sectors on the card, or 0 if an error occurred.
*
* @details This function sends a CMD9 command to the card to get the Card Specific
* Data (CSD) and then extracts the number of sectors from the CSD.
*/
static uint32_t in_sd_spi_sectors(sd_card_t *sd_card_p) {
// CMD9, Response R2 (R1 byte + 16-byte block read)
if (sd_cmd(sd_card_p, CMD9_SEND_CSD, 0x0, false, 0) != 0x0) {
DBG_PRINTF("Didn't get a response from the disk\n");
return 0;
}
if (read_bytes(sd_card_p, sd_card_p->state.CSD, 16) != 0) {
DBG_PRINTF("Couldn't read CSD response from disk\n");
return 0;
}
return CSD_sectors(sd_card_p->state.CSD);
}
/**
* @brief Get the number of sectors on an SD card.
*
* @param sd_card_p A pointer to the sd_card_t structure for the card.
*
* @return The number of sectors on the card, or 0 if an error occurred.
*
* @details This function gets the number of sectors by first acquiring the card,
* then calling in_sd_spi_sectors to get the number of sectors, and finally
* releasing the card.
*/
uint32_t sd_spi_sectors(sd_card_t *sd_card_p) {
sd_acquire(sd_card_p);
uint32_t sectors = in_sd_spi_sectors(sd_card_p);
sd_release(sd_card_p);
return sectors;
}
/**
* @brief Wait for the card to become ready and send a given token.
*
* @param sd_card_p A pointer to the sd_card_t structure for the card.
* @param token The token to be sent once the card is ready.
*
* @return True if the card became ready and the token was sent, false otherwise.
*
* @details The function waits until the card is ready and then sends the given
* token. If the card doesn't become ready within the timeout period,
* the function returns false.
*/
static bool sd_wait_token(sd_card_t *sd_card_p, uint8_t token) {
//TRACE_PRINTF("%s(0x%02x)\n", __FUNCTION__, token);
uint32_t start = millis();
do {
if (token == sd_spi_read(sd_card_p)) {
return true;
}
} while (millis() - start < sd_timeouts.sd_command);
DBG_PRINTF("sd_wait_token: timeout\n");
return false;
}
static bool chk_crc16(uint8_t *buffer, size_t length, uint16_t crc) {
if (crc_on) {
uint16_t crc_result;
// Compute and verify checksum
crc_result = crc16(buffer, length);
if (crc_result != crc)
DBG_PRINTF("%s: Invalid CRC received: 0x%" PRIx16 " computed: 0x%" PRIx16 "\n",
__func__, crc, crc_result);
return (crc_result == crc);
}
return true;
}
#define SPI_START_BLOCK (0xFE) /* For Single Block Read/Write and Multiple Block Read */
static block_dev_err_t stop_wr_tran(sd_card_t *sd_card_p);
static block_dev_err_t read_bytes(sd_card_t *sd_card_p, uint8_t *buffer, uint32_t length) {
uint16_t crc;
// read until start byte (0xFE)
if (false == sd_wait_token(sd_card_p, SPI_START_BLOCK)) {
DBG_PRINTF("%s:%d Read timeout\n", __func__, __LINE__);
return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
}
bool ok = sd_spi_transfer(sd_card_p, NULL, buffer, length);
if (!ok) return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
// Read the CRC16 checksum for the data block
crc = (sd_spi_read(sd_card_p) << 8);
crc |= sd_spi_read(sd_card_p);
if (!chk_crc16(buffer, length, crc)) {
DBG_PRINTF("%s: Invalid CRC received: 0x%" PRIx16 "\n", __func__, crc);
return SD_BLOCK_DEVICE_ERROR_CRC;
}
return 0;
}
/**
* @brief Read a block of data from the SD card.
*
* @param sd_card_p pointer to sd_card_t structure
* @param buffer pointer to the buffer to store the data
* @param data_address the address of the block to read
* @param num_rd_blks the number of blocks to read
*
* @return error code
*
* @details
* This function checks if the SD card is initialized and has a valid disk,
* and if the number of blocks to read is not zero and is within the range of
* the card's sectors. If not, it returns SD_BLOCK_DEVICE_ERROR_PARAMETER.
* If there is an ongoing write transmission, it stops it.
* It then sends a command to receive data based on
* the number of blocks to read. It reads the data from the SD card and checks
* the CRC16 checksum for each block. If the two match, the function continues
* to the next block. If the number of blocks to read is greater than 1, it
* sends CMD12 to stop the transmission after all blocks have been
* read. It then checks the CRC16 checksum for the last block and returns the
* error code.
*/
static block_dev_err_t in_sd_read_blocks(sd_card_t *sd_card_p, uint8_t *buffer,
const uint32_t data_address,
const uint32_t num_rd_blks) {
if (sd_card_p->state.m_Status & (STA_NOINIT | STA_NODISK))
return SD_BLOCK_DEVICE_ERROR_PARAMETER;
if (!num_rd_blks) return SD_BLOCK_DEVICE_ERROR_PARAMETER;
if (data_address + num_rd_blks > sd_card_p->state.sectors)
return SD_BLOCK_DEVICE_ERROR_PARAMETER;
block_dev_err_t status = SD_BLOCK_DEVICE_ERROR_NONE;
// Stop any ongoing write transmission
if (sd_card_p->spi_if_p->state.ongoing_mlt_blk_wrt) {
status = stop_wr_tran(sd_card_p);
if (SD_BLOCK_DEVICE_ERROR_NONE != status) return status;
}
// Send command to receive data
if (num_rd_blks == 1)
status = sd_cmd(sd_card_p, CMD17_READ_SINGLE_BLOCK, data_address, false, 0);
else
status = sd_cmd(sd_card_p, CMD18_READ_MULTIPLE_BLOCK, data_address, false, 0);
if (SD_BLOCK_DEVICE_ERROR_NONE != status) return status;
/* Optimization:
While the DMA is busy transfering the block data,
use the some of the wait time to check the CRC
for the previous block.
*/
uint16_t prev_block_crc = 0;
uint8_t *prev_buffer_addr = 0;
uint32_t blk_cnt = num_rd_blks;
// receive the data : one block at a time
while (blk_cnt) {
// read until start byte (0xFE)
if (!sd_wait_token(sd_card_p, SPI_START_BLOCK)) {
DBG_PRINTF("%s:%d Read timeout\n", __func__, __LINE__);
return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
}
// read data
sd_spi_transfer_start(sd_card_p, NULL, buffer, sd_block_size);
// Check the CRC16 checksum for the previous data block
if (prev_buffer_addr) {
// Check previous block's CRC:
if (!chk_crc16(prev_buffer_addr, sd_block_size, prev_block_crc)) {
DBG_PRINTF("%s: Invalid CRC received: 0x%" PRIx16 "\n", __func__,
prev_block_crc);
return SD_BLOCK_DEVICE_ERROR_CRC;
}
}
uint32_t timeout = calculate_transfer_time_ms(sd_card_p->spi_if_p->spi, sd_block_size);
bool ok = sd_spi_transfer_wait_complete(sd_card_p, timeout);
if (!ok) return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
// Read the CRC16 checksum for the data block
prev_block_crc = sd_spi_read(sd_card_p) << 8;
prev_block_crc |= sd_spi_read(sd_card_p);
prev_buffer_addr = buffer;
buffer += sd_block_size;
--blk_cnt;
}
if (num_rd_blks > 1) {
// Send CMD12(0x00000000) to stop the transmission for multi-block transfer
status = sd_cmd(sd_card_p, CMD12_STOP_TRANSMISSION, 0x0, false, 0);
if (SD_BLOCK_DEVICE_ERROR_NONE != status) return status;
}
// Check final block's CRC:
if (!chk_crc16(prev_buffer_addr, sd_block_size, prev_block_crc)) {
DBG_PRINTF("%s: Invalid CRC received: 0x%" PRIx16 "\n", __func__, prev_block_crc);
return SD_BLOCK_DEVICE_ERROR_CRC;
}
return status;
}
static block_dev_err_t sd_read_blocks(sd_card_t *sd_card_p, uint8_t *buffer,
uint32_t data_address, uint32_t num_rd_blks) {
TRACE_PRINTF("sd_read_blocks(0x%p, 0x%lx, 0x%lx)\n", buffer, data_address, num_rd_blks);
sd_acquire(sd_card_p);
unsigned retries = sd_timeouts.sd_command_retries;
block_dev_err_t status;
do {
status = in_sd_read_blocks(sd_card_p, buffer, data_address, num_rd_blks);
if (status != SD_BLOCK_DEVICE_ERROR_NONE) {
if (SD_BLOCK_DEVICE_ERROR_NONE !=
sd_cmd(sd_card_p, CMD12_STOP_TRANSMISSION, 0x0, false, 0))
return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
}
} while (--retries && status != SD_BLOCK_DEVICE_ERROR_NONE);
sd_release(sd_card_p);
return status;
}
/**
* @brief Send the numbers of the well written (without errors) blocks.
*
* This function sends the ACMD22 command to the SD card to get the number of
* blocks that were successfully written without errors. It then reads the
* response from the SD card and returns it.
*
* @param sd_card_p Pointer to the SD card object.
* @param num_p Pointer to a variable to store the number of blocks.
*
* @return Block device error code. Returns SD_BLOCK_DEVICE_ERROR_NONE on success,
* SD_BLOCK_DEVICE_ERROR_NO_DEVICE if the SD card is not responding,
* SD_BLOCK_DEVICE_ERROR_CRC if there was a CRC error reading the response,
* SD_BLOCK_DEVICE_ERROR_PARAMETER if an invalid parameter was passed.
*/
static block_dev_err_t get_num_wr_blocks(sd_card_t *sd_card_p, uint32_t *num_p) {
// Send the ACMD22 command to get the number of written blocks
block_dev_err_t err = sd_cmd(sd_card_p, ACMD22_SEND_NUM_WR_BLOCKS, 0, true, NULL);
// If the command was not successful, return the error code
if (SD_BLOCK_DEVICE_ERROR_NONE != err) {
DBG_PRINTF("Didn't get a response from the disk\n");
return err;
}
// Read the response from the SD card and store it in the num_p variable
err = read_bytes(sd_card_p, (uint8_t *)num_p, sizeof(uint32_t));
*num_p = __builtin_bswap32(*num_p);
// If there was an error reading the response, return the error code
if (SD_BLOCK_DEVICE_ERROR_NONE != err) {
DBG_PRINTF("Couldn't read NUM_WR_BLOCKS response from disk\n");
return err;
}
// Return success
return SD_BLOCK_DEVICE_ERROR_NONE;
}
/**
* @brief Send a single block of data to the SD card.
*
* @param sd_card_p Pointer to the SD card object.
* @param buffer Pointer to the buffer containing the data to be sent.
* @param token The token to be sent before the data.
* @param length The length of the data to be sent.
*
* @return Block device error code.
*
* @details
* The function sends a single block of data to the SD card. It starts by sending the start block
* token, then writes the data using the SPI transfer function. While the SPI transfer is ongoing,
* the function computes the CRC16 checksum of the data if CRC checking is enabled. After the SPI
* transfer is complete, the function writes the CRC16 checksum to the SD card. Finally, the function
* checks the response token and returns an error code if the data was not accepted.
*/
static block_dev_err_t send_block(sd_card_t *sd_card_p, const uint8_t *buffer, uint8_t token,
uint32_t length)
{
uint8_t response;
/* Indicate start of block - Start Block Token */
response = sd_spi_write_read(sd_card_p, token);
if (!response) {
DBG_PRINTF("Start Block Token not accepted. Response: 0x%x\n", response);
return SD_BLOCK_DEVICE_ERROR_WRITE;
}
// Write the data
sd_spi_transfer_start(sd_card_p, buffer, NULL, length);
/* Optimization:
While the DMA is busy transfering the block data,
use the some of the wait time to calculate the CRC.
Typically, DMA transfer of the block data takes about 244 us,
but the CRC16 calculation takes only about 66 us.
*/
uint16_t crc = (~0);
// While DMA transfers the block, compute CRC:
if (crc_on) {
// Compute CRC
crc = crc16((void *)buffer, length);
}
uint32_t timeout = calculate_transfer_time_ms(sd_card_p->spi_if_p->spi, length);
bool ok = sd_spi_transfer_wait_complete(sd_card_p, timeout);
if (!ok) return SD_BLOCK_DEVICE_ERROR_WRITE;
// Write the checksum CRC16
sd_spi_write(sd_card_p, crc >> 8);
sd_spi_write(sd_card_p, crc);
block_dev_err_t rc = SD_BLOCK_DEVICE_ERROR_NONE;
// Check the response token
response = sd_spi_read(sd_card_p);
// Only CRC and general write error are communicated via response token
if ((response & SPI_DATA_RESPONSE_MASK) != SPI_DATA_ACCEPTED) {
EMSG_PRINTF("%s: Block Write not accepted. Response token: 0x%x, "
"status bits: %d%d%d\n",
sd_get_drive_prefix(sd_card_p),
response,
response & 0b1000 ? 1 : 0,
response & 0b0100 ? 1 : 0,
response & 0b0010 ? 1 : 0
);
/*
* The meaning of the status bits (bits 3, 2 & 1)
* is defined as follows:
* '010' - Data accepted.
* '101' - Data rejected due to a CRC error.
* '110' - Data Rejected due to a Write Error
* In case of any error (CRC or Write Error) during Write Multiple Block operation, the
* host shall stop the data transmission using CMD12. In case of a Write Error (response
* '110'), the host may send CMD13 (SEND_STATUS) in order to get the cause of the write
* problem. ACMD22 can be used to find the number of well written write blocks.
*/
rc = SD_BLOCK_DEVICE_ERROR_WRITE;
}
// Wait while card is busy programming
if (false == sd_wait_ready(sd_card_p, sd_timeouts.sd_command)) {
DBG_PRINTF("%s:%d: Card not ready yet\n", __func__, __LINE__);
rc = SD_BLOCK_DEVICE_ERROR_WRITE;
}
return rc;
}
/**
* @brief Send all blocks of data, one block at a time.
*
* The function performs the following steps:
* - Sends each block of data using the send_block() function
* - Checks the status of each send operation and stop if there is an error
* - Updates the buffer pointer and data address after each send operation
* - Sets the ongoing_mlt_blk_wrt flag to true if all blocks are sent successfully
* - Otherwise, stops the ongoing multiblock write and resets the number of
* blocks requested
*
* @param sd_card_p Pointer to the SD card object.
* @param buffer_p Pointer to the array of const uint8_t pointers. Each pointer
* points to the start of the block of data to be written.
* @param data_address_p Pointer to the address of the first block of data to be
* written.
* @param num_wrt_blks_p Pointer to the number of blocks to be written.
*
* @return SD_BLOCK_DEVICE_ERROR_NONE if all blocks are sent successfully,
* otherwise an error code.
*/
static block_dev_err_t send_all_blocks(sd_card_t *sd_card_p, const uint8_t *buffer_p[],
uint32_t * const data_address_p,
uint32_t * const num_wrt_blks_p)
{
block_dev_err_t status;
do {
status = send_block(sd_card_p, *buffer_p, SPI_START_BLK_MUL_WRITE, sd_block_size);
if (SD_BLOCK_DEVICE_ERROR_NONE != status) break;
*buffer_p += sd_block_size;
++*data_address_p;
} while (--*num_wrt_blks_p);
if (SD_BLOCK_DEVICE_ERROR_NONE == status) {
myASSERT(!*num_wrt_blks_p);
sd_card_p->spi_if_p->state.cont_sector_wrt = *data_address_p;
sd_card_p->spi_if_p->state.ongoing_mlt_blk_wrt = true;
} else {
// sd_card_p->spi_if_p->state.n_wrt_blks_reqd cleared in stop_wr_tran
uint32_t n_wrt_blks_reqd = sd_card_p->spi_if_p->state.n_wrt_blks_reqd;
stop_wr_tran(sd_card_p); // Ignore return value
uint32_t nw;
block_dev_err_t err = get_num_wr_blocks(sd_card_p, &nw);
if (SD_BLOCK_DEVICE_ERROR_NONE == err) {
DBG_PRINTF("blocks_requested: %lu, NUM_WR_BLOCKS: %lu\n",
n_wrt_blks_reqd, nw);
*num_wrt_blks_p = n_wrt_blks_reqd - nw;
}
}
return status;
}
/**
* @brief Write multiple blocks of data to the SD card.
*
* If there is an ongoing multiblock write and the next write is contiguous,
* this function will continue the write operation without stopping the
* transmission. Otherwise, it will stop any ongoing write transmission
* and send the command to perform the write operation.
*
* @param sd_card_p Pointer to the SD card object.
* @param buffer_p Pointer to the array of const uint8_t pointers. Each pointer
* points to the data buffer for a block.
* @param data_address_p Pointer to the integer storing the data address.
* @param num_wrt_blks_p Pointer to the integer storing the number of blocks to
* write.
* @return block_dev_err_t Error code indicating the status of the write operation.
*/
static block_dev_err_t in_sd_write_blocks(sd_card_t *sd_card_p,
const uint8_t *buffer_p[],
uint32_t * const data_address_p,
uint32_t * const num_wrt_blks_p)
{
block_dev_err_t status = SD_BLOCK_DEVICE_ERROR_NONE;
/* Continue a multiblock write */
if (sd_card_p->spi_if_p->state.ongoing_mlt_blk_wrt &&
sd_card_p->spi_if_p->state.cont_sector_wrt == *data_address_p) {
// Update the number of blocks requested for write
sd_card_p->spi_if_p->state.n_wrt_blks_reqd += *num_wrt_blks_p;
// Send all blocks of data
return send_all_blocks(sd_card_p, buffer_p, data_address_p, num_wrt_blks_p);
}
// Stop any ongoing write transmission
if (sd_card_p->spi_if_p->state.ongoing_mlt_blk_wrt) {
status = stop_wr_tran(sd_card_p);
if (SD_BLOCK_DEVICE_ERROR_NONE != status) return status;
}
// Send command to perform write operation
status = sd_cmd(sd_card_p, CMD25_WRITE_MULTIPLE_BLOCK, *data_address_p, false, 0);
if (SD_BLOCK_DEVICE_ERROR_NONE != status) return status;
// Update the number of blocks requested for write
sd_card_p->spi_if_p->state.n_wrt_blks_reqd = *num_wrt_blks_p;
// Send all blocks of data
return send_all_blocks(sd_card_p, buffer_p, data_address_p, num_wrt_blks_p);
/* Optimization:
To optimize large contiguous writes,
postpone stopping transmission until it is
clear that the next operation is not a continuation.
Any transactions other than a `sd_write_blocks`
continuation must stop any ongoing transmission
before proceeding.
*/
}
static block_dev_err_t stop_wr_tran(sd_card_t *sd_card_p) {
sd_card_p->spi_if_p->state.ongoing_mlt_blk_wrt = false;
/* In a Multiple Block write operation, the stop transmission will be
* done by sending 'Stop Tran' token instead of 'Start Block' token at
* the beginning of the next block
*/
sd_spi_write(sd_card_p, SPI_STOP_TRAN);
/*
Once the programming operation is completed, the
host must check the results of the programming
using the SEND_STATUS command (CMD13).
Some errors (e.g. address out of range, write
protect violation, etc.) are detected during
programming only. The only validation check
performed on the data block and communicated to
the host via the data-response token is CRC.
*/
if (false == sd_wait_ready(sd_card_p, sd_timeouts.sd_command)) {
DBG_PRINTF("Card not ready yet\n");
}
uint32_t stat = 0;
sd_card_p->spi_if_p->state.n_wrt_blks_reqd = 0;
return sd_cmd(sd_card_p, CMD13_SEND_STATUS, 0, false, &stat);
}
/**
* @brief Writes a single block to the SD card
*
* @param[in] sd_card_p Pointer to the SD card
* @param[in] buffer Buffer of data to write to the block
* @param[in] address Logical Address of the block to write to (LBA)
*
* @return
* - SD_BLOCK_DEVICE_ERROR_NONE on success
* - error code on failure
*/
static block_dev_err_t write_block(sd_card_t *sd_card_p, const uint8_t *buffer,
uint32_t const address)
{
// Stop any ongoing multiple block write transmission
block_dev_err_t status = SD_BLOCK_DEVICE_ERROR_NONE;
if (sd_card_p->spi_if_p->state.ongoing_mlt_blk_wrt) {
status = stop_wr_tran(sd_card_p);
if (SD_BLOCK_DEVICE_ERROR_NONE != status) return status;
}
// Send command to perform the write operation
status = sd_cmd(sd_card_p, CMD24_WRITE_BLOCK, address, false, 0);
if (SD_BLOCK_DEVICE_ERROR_NONE != status) return status;
// Write data
send_block(sd_card_p, buffer, SPI_START_BLOCK, sd_block_size);
/*
Once the programming operation is completed, the
host must check the results of the programming
using the SEND_STATUS command (CMD13).
Some errors (e.g. address out of range, write
protect violation, etc.) are detected during
programming only. The only validation check
performed on the data block and communicated to
the host via the data-response token is CRC.
*/
// Send command to get the status of the card
uint32_t stat = 0;
status = sd_cmd(sd_card_p, CMD13_SEND_STATUS, 0, false, &stat);
return status;
}
/**
* @brief Programs blocks to a block device
*
* @param[in] sd_card_p Pointer to the SD card
* @param[in] buffer Buffer of data to write to blocks
* @param[in] data_address Logical Address of block to begin writing to (LBA)
* @param[in] num_wrt_blks Size to write in blocks
*
* @return
* - SD_BLOCK_DEVICE_ERROR_NONE on success
* - SD_BLOCK_DEVICE_ERROR_NO_DEVICE if the device (SD card) is missing or not connected
* - SD_BLOCK_DEVICE_ERROR_CRC if there was a CRC error
* - SD_BLOCK_DEVICE_ERROR_PARAMETER if an invalid parameter was passed
* - SD_BLOCK_DEVICE_ERROR_UNSUPPORTED if the command is unsupported
* - SD_BLOCK_DEVICE_ERROR_NO_INIT if the device is not initialized
* - SD_BLOCK_DEVICE_ERROR_WRITE if there was an SPI write error
* - SD_BLOCK_DEVICE_ERROR_ERASE if there was an erase error
*/
static block_dev_err_t sd_write_blocks(sd_card_t *sd_card_p, uint8_t const buffer[],
uint32_t data_address, uint32_t num_wrt_blks)
{
TRACE_PRINTF("%s(0x%p, 0x%lx, 0x%lx)\n", __func__, buffer, data_address, num_wrt_blks);
// Check if the SD card pointer is valid
if (NULL == sd_card_p)
return SD_BLOCK_DEVICE_ERROR_PARAMETER;
// Check if the device is initialized and not missing
if (sd_card_p->state.m_Status & (STA_NOINIT | STA_NODISK))
return SD_BLOCK_DEVICE_ERROR_PARAMETER;
// Check if the number of blocks to write is valid
if (!num_wrt_blks) return SD_BLOCK_DEVICE_ERROR_PARAMETER;
// Calculate the end address
uint32_t end_address = data_address + num_wrt_blks;
// Check if the end address is within the device's boundaries
if (end_address >= sd_card_p->state.sectors)
return SD_BLOCK_DEVICE_ERROR_PARAMETER;
// Acquire the SD card
sd_acquire(sd_card_p);
block_dev_err_t status;
// If writing only one block, use the optimized function
if (1 == num_wrt_blks) {
status = write_block(sd_card_p, buffer, data_address);
} else {
// If writing multiple blocks, retry the operation until it succeeds or reaches the maximum number of retries
unsigned retries = sd_timeouts.sd_command_retries;
do {
if (retries < sd_timeouts.sd_command_retries) DBG_PRINTF("Retrying\n");
status = in_sd_write_blocks(sd_card_p, &buffer, &data_address, &num_wrt_blks);
if (SD_BLOCK_DEVICE_ERROR_WRITE == status)
DBG_PRINTF("%s status=0x%x data_address=%lu num_wrt_blks=%lu\n", sd_get_drive_prefix(sd_card_p), status, data_address, num_wrt_blks);
} while (SD_BLOCK_DEVICE_ERROR_WRITE == status && --retries && num_wrt_blks);
}
// Release the SD card
sd_release(sd_card_p);
return status;
}
/**
* @brief Synchronize the SD card
*
* This function is used to ensure that any ongoing write operations are
* completed before the SD card is released. This is necessary to ensure
* that the SD card is not released while it is still busy with a write
* operation.
*
* @param sd_card_p Pointer to the SD card object.
*
* @return
* - SD_BLOCK_DEVICE_ERROR_NONE on success
* - SD_BLOCK_DEVICE_ERROR_WRITE if there was a write error
* - SD_BLOCK_DEVICE_ERROR_UNSUPPORTED if the command is unsupported
* - SD_BLOCK_DEVICE_ERROR_NO_INIT if the device is not initialized
* - SD_BLOCK_DEVICE_ERROR_NO_DEVICE if the device (SD card) is missing or not connected
*/
static block_dev_err_t sd_sync(sd_card_t *sd_card_p) {
block_dev_err_t status = SD_BLOCK_DEVICE_ERROR_NONE;
sd_acquire(sd_card_p);
// Stop any ongoing transmission
if (sd_card_p->spi_if_p->state.ongoing_mlt_blk_wrt) status = stop_wr_tran(sd_card_p);
sd_release(sd_card_p);
return status;
}
/*!< Number of retries for sending CMDO */
#define SD_CMD0_GO_IDLE_STATE_RETRIES 10
/**
* @brief Resets the SD card to the idle state.
*
* This function sends the initializing sequence to the SD card and waits for it to enter the idle state.
*
* @param sd_card_p Pointer to the SD card object.
* @return The response from the SD card.
* @retval R1_IDLE_STATE if the SD card successfully entered the idle state.
* @retval R1_NO_RESPONSE if the SD card did not respond.
*/
static uint32_t in_sd_go_idle_state(sd_card_t *sd_card_p) {
/*
Power ON or card insertion
After supply voltage reached above 2.2 volts,
wait for one millisecond at least.
Set SPI clock rate between 100 kHz and 400 kHz.
Set DI and CS high and apply 74 or more clock pulses to SCLK.
The card will enter its native operating mode and go ready to accept native
command.
*/
sd_spi_go_low_frequency(sd_card_p);
uint32_t response = R1_NO_RESPONSE;
/* Resetting the MCU SPI master may not reset the on-board SDCard, in which
* case when MCU power-on occurs the SDCard will resume operations as
* though there was no reset. In this scenario the first CMD0 will
* not be interpreted as a command and get lost. For some cards retrying
* the command overcomes this situation. */
for (int i = 0; i < SD_CMD0_GO_IDLE_STATE_RETRIES; i++) {
/*
After power up, the host starts the clock and sends the initializing sequence on the CMD line.
This sequence is a contiguous stream of logical ‘1’s. The sequence length is the maximum of 1msec,
74 clocks or the supply-ramp-uptime; the additional 10 clocks
(over the 64 clocks after what the card should be ready for communication) is
provided to eliminate power-up synchronization problems.
*/
// Set DI and CS high and apply 74 or more clock pulses to SCLK:
sd_spi_deselect(sd_card_p);
uint8_t ones[10];
memset(ones, 0xFF, sizeof ones);
uint32_t start = millis();
do {
spi_transfer(sd_card_p->spi_if_p->spi, ones, NULL, sizeof ones);
} while (millis() - start < 1);
sd_spi_select(sd_card_p);
sd_cmd(sd_card_p, CMD0_GO_IDLE_STATE, 0x0, false, &response);
if (R1_IDLE_STATE == response) {
break;
}
}
return response;
}
/**
* @brief Resets the SD card to the idle state.
*
* This function sends the initializing sequence to the SD card and waits for it to enter the idle state.
*
* @param sd_card_p Pointer to the SD card object.
* @return The response from the SD card.
* @retval R1_IDLE_STATE if the SD card successfully entered the idle state.
* @retval R1_NO_RESPONSE if the SD card did not respond.
*/
uint32_t sd_go_idle_state(sd_card_t *sd_card_p) {
sd_spi_lock(sd_card_p);
uint32_t response = in_sd_go_idle_state(sd_card_p);
sd_spi_release(sd_card_p);
return response;
}
/**
* @brief Initializes the SD card medium.
*
* This function initializes the SD card medium by following the SD card initialization sequence.
* It transitions the card from SD card mode to SPI mode by sending the CMD0 command and applying
* the initializing sequence. It then sends the CMD8 command to check if the card supports the
* SD version 2.0 specification. If the card rejects the command, it assumes the card is using the
* legacy protocol or is a MMC card. It then enables or disables CRC based on the crc_on flag.
* It reads the OCR (Operating Conditions Register) using the CMD58 command and checks if the card
* supports the voltage range of 3.3V. If not, it sets the card type to CARD_UNKNOWN and returns
* SD_BLOCK_DEVICE_ERROR_UNUSABLE. It sets the card type based on the response of the ACMD41 command.
* If the initialization is successful, it disables or enables CRC and sets the card type based on
* the response of the CMD58 command. It then disconnects the 50 KOhm pull-up resistor on CS (pin 1)
* of the card using the ACMD42_SET_CLR_CARD_DETECT command.
*
* @param sd_card_p Pointer to the SD card object.
* @return The block device error status.
* @retval SD_BLOCK_DEVICE_ERROR_NONE if the initialization is successful.
* @retval SD_BLOCK_DEVICE_ERROR_NO_DEVICE if the card did not respond.
* @retval SD_BLOCK_DEVICE_ERROR_UNUSABLE if the card does not support the voltage range.
*/
static block_dev_err_t sd_init_medium(sd_card_t *sd_card_p) {
int32_t status = SD_BLOCK_DEVICE_ERROR_NONE;
uint32_t response, arg;
// The card is transitioned from SDCard mode to SPI mode by sending the CMD0
// + CS Asserted("0")
if (in_sd_go_idle_state(sd_card_p) != R1_IDLE_STATE) {
EMSG_PRINTF("No disk, or could not put SD card in to SPI idle state\n");
return SD_BLOCK_DEVICE_ERROR_NO_DEVICE;
}
// Send CMD8, if the card rejects the command then it's probably using the
// legacy protocol, or is a MMC, or just flat-out broken
status = sd_cmd8(sd_card_p);
if (SD_BLOCK_DEVICE_ERROR_NONE != status && SD_BLOCK_DEVICE_ERROR_UNSUPPORTED != status) {
return status;
}
if (crc_on) {
size_t retries = 3;
do {
// Enable CRC
status = sd_cmd(sd_card_p, CMD59_CRC_ON_OFF, 1, false, 0);
} while (--retries && (SD_BLOCK_DEVICE_ERROR_NONE != status));
}
// Read OCR - CMD58 Response contains OCR register
if (SD_BLOCK_DEVICE_ERROR_NONE !=
(status = sd_cmd(sd_card_p, CMD58_READ_OCR, 0x0, false, &response))) {
return status;
}
// Check if card supports voltage range: 3.3V
if (!(response & OCR_3_3V)) {
sd_card_p->state.card_type = CARD_UNKNOWN;
status = SD_BLOCK_DEVICE_ERROR_UNUSABLE;
return status;
}
// HCS is set 1 for HC/XC capacity cards for ACMD41, if supported
arg = 0x0;
if (SDCARD_V2 == sd_card_p->state.card_type) {
arg |= OCR_HCS_CCS;
}
/* Idle state bit in the R1 response of ACMD41 is used by the card to inform
* the host if initialization of ACMD41 is completed. "1" indicates that the
* card is still initializing. "0" indicates completion of initialization.
* The host repeatedly issues ACMD41 until this bit is set to "0".
*/
uint32_t start = millis();
do {
status = sd_cmd(sd_card_p, ACMD41_SD_SEND_OP_COND, arg, true, &response);
} while (response & R1_IDLE_STATE && millis() - start < sd_timeouts.sd_command);
// Initialization complete: ACMD41 successful
if ((SD_BLOCK_DEVICE_ERROR_NONE != status) || (0x00 != response)) {
sd_card_p->state.card_type = CARD_UNKNOWN;
EMSG_PRINTF("Timeout waiting for card\n");
return status;
}
if (SDCARD_V2 == sd_card_p->state.card_type) {
// Get the card capacity CCS: CMD58
if (SD_BLOCK_DEVICE_ERROR_NONE ==
(status = sd_cmd(sd_card_p, CMD58_READ_OCR, 0x0, false, &response))) {
// High Capacity card
if (response & OCR_HCS_CCS) {
sd_card_p->state.card_type = SDCARD_V2HC;
DBG_PRINTF("Card Initialized: High Capacity Card\n");
} else {
DBG_PRINTF("Card Initialized: Standard Capacity Card: Version 2.x\n");
}
}
} else {
sd_card_p->state.card_type = SDCARD_V1;
DBG_PRINTF("Card Initialized: Version 1.x Card\n");
}
if (!crc_on) {
// Disable CRC
status = sd_cmd(sd_card_p, CMD59_CRC_ON_OFF, 0, false, 0);
}
/* Disconnect the 50 KOhm pull-up resistor on CS (pin 1) of the card.
The pull-up may be used for card detection.
At power up this line has a 50KOhm pull up enabled in the card.
This resistor serves two functions Card detection and Mode Selection.
For Mode Selection, the host can drive the line high or let it be pulled high to select SD
mode. If the host wants to select SPI mode it should drive the line low. For Card detection,
the host detects that the line is pulled high. This pull-up should be disconnected by the
user, during regular data transfer, with SET_CLR_CARD_DETECT (ACMD42) command. */
status = sd_cmd(sd_card_p, ACMD42_SET_CLR_CARD_DETECT, 0, true, NULL);
return status;
}
/**
* @brief Tests the communication with the SD card.
*
* This function is used to test the communication with the SD card. It first checks if the
* SD card is already initialized, and if so, it sends a command to get the card status. If the
* card status is not received, it assumes that the card is no longer present and sets the
* `STA_NOINIT` flag in the card status. If the card is not initialized, it performs a light
* version of the initialization to test the communication. It sends the initializing sequence
* and waits for the card to go idle. If the card responds with a response status, it assumes
* that the communication is successful and returns `true`. If the card does not respond, it
* assumes that something is holding the DO line and returns `false`.
*
* @param sd_card_p Pointer to the SD card object.
* @return `true` if the communication with the SD card is successful, `false` otherwise.
*/
static bool sd_spi_test_com(sd_card_t *sd_card_p) {
// This is allowed to be called before initialization, so ensure mutex is created
if (!mutex_is_initialized(&sd_card_p->state.mutex)) mutex_init(&sd_card_p->state.mutex);
sd_acquire(sd_card_p);
bool success = false;
if (!(sd_card_p->state.m_Status & STA_NOINIT)) {
// SD card is currently initialized
// Timeout of 0 means only check once
if (sd_wait_ready(sd_card_p, 0)) {
// DO has been released, try to get status
uint32_t response;
for (unsigned i = 0; i < sd_timeouts.sd_command_retries; i++) {
// Send command over SPI interface
response = sd_cmd_spi(sd_card_p, CMD13_SEND_STATUS, 0);
if (R1_NO_RESPONSE != response) {
// Got a response!
success = true;
break;
}
}
if (!success) {
// Card no longer sensed - ensure card is initialized once re-attached
sd_card_p->state.m_Status |= STA_NOINIT;
}
} else {
// SD card is currently holding DO which is sufficient enough to know it's still
// there
success = true;
}
} else {
// Do a "light" version of init, just enough to test com
// Initialize the member variables
sd_card_p->state.card_type = SDCARD_NONE;
sd_spi_go_low_frequency(sd_card_p);
sd_spi_send_initializing_sequence(sd_card_p);
if (sd_wait_ready(sd_card_p, 0)) {
// DO has been released, try to make SD card go idle
uint32_t response;
for (unsigned i = 0; i < sd_timeouts.sd_command_retries; i++) {
// Send command over SPI interface
response = sd_cmd_spi(sd_card_p, CMD0_GO_IDLE_STATE, 0);
if (R1_NO_RESPONSE != response) {
// Got a response!
success = true;
break;
}
}
} else {
// Something is holding DO - better to return false and allow user to try again
// later
success = false;
}
}
sd_release(sd_card_p);
return success;
}
/**
* @brief Initializes the SD card over SPI.
*
* This function initializes the SD card over SPI. It first checks if the SD card is already
* initialized, and if so, it returns the current status. If the card is not initialized, it
* performs the initialization sequence and returns the status of the card. The card is
* initialized by sending the initializing sequence, checking the card type, setting the SCK
* for data transfer, and setting the block length to 512. The card is now considered initialized
* and its status is returned.
*
* @param sd_card_p Pointer to the SD card object.
* @return The status of the SD card:
* STA_NOINIT = 0x01, // Drive not initialized
* STA_NODISK = 0x02, // No medium in the drive
* STA_PROTECT = 0x04 // Write protected
*/
DSTATUS sd_card_spi_init(sd_card_t *sd_card_p) {
TRACE_PRINTF("> %s\n", __FUNCTION__);
// Acquire the lock
sd_lock(sd_card_p);
// Check if there's a card in the socket before proceeding
sd_card_detect(sd_card_p);
if (sd_card_p->state.m_Status & STA_NODISK) {
// Release the lock and return the current status
sd_unlock(sd_card_p);
return sd_card_p->state.m_Status;
}
// Check if we're not already initialized before proceeding
if (!(sd_card_p->state.m_Status & STA_NOINIT)) {
// Release the lock and return the current status
sd_unlock(sd_card_p);
return sd_card_p->state.m_Status;
}
// Initialize the member variables
sd_card_p->state.card_type = SDCARD_NONE;
// Acquire the SD card
sd_spi_acquire(sd_card_p);
// Initialize the medium
int err = sd_init_medium(sd_card_p);
if (SD_BLOCK_DEVICE_ERROR_NONE != err) {
EMSG_PRINTF("Failed to initialize card\n");
sd_release(sd_card_p);
return sd_card_p->state.m_Status;
}
// No support for SDSC Card (CCS=0) with byte unit address
if (SDCARD_V2HC != sd_card_p->state.card_type) {
EMSG_PRINTF("SD Standard Capacity Memory Card unsupported\n");
sd_release(sd_card_p);
return sd_card_p->state.m_Status;
}
DBG_PRINTF("SD card initialized\n");
// Set SCK for data transfer
sd_spi_go_high_frequency(sd_card_p);
// Get the number of sectors on the card
sd_card_p->state.sectors = in_sd_spi_sectors(sd_card_p);
if (0 == sd_card_p->state.sectors) {
// CMD9 failed
sd_release(sd_card_p);
return sd_card_p->state.m_Status;
}
// Get the CID of the card
if (SD_BLOCK_DEVICE_ERROR_NONE != sd_cmd(sd_card_p, CMD10_SEND_CID, 0x0, false, 0)) {
DBG_PRINTF("Didn't get a response from the disk\n");
sd_release(sd_card_p);
return sd_card_p->state.m_Status;
}
if (read_bytes(sd_card_p, (uint8_t *)&sd_card_p->state.CID, sizeof(CID_t)) != 0) {
DBG_PRINTF("Couldn't read CID response from disk\n");
sd_release(sd_card_p);
return sd_card_p->state.m_Status;
}
// Set the block length to 512 (CMD16)
if (SD_BLOCK_DEVICE_ERROR_NONE !=
sd_cmd(sd_card_p, CMD16_SET_BLOCKLEN, sd_block_size, false, 0)) {
DBG_PRINTF("Set %u-byte block timed out\n", sd_block_size);
sd_release(sd_card_p);
return sd_card_p->state.m_Status;
}
// The card is now initialized
sd_card_p->state.m_Status &= ~STA_NOINIT;
// Release the SD card
sd_release(sd_card_p);
// Return the disk status
return sd_card_p->state.m_Status;
}
/**
* @brief Deinitializes the SD card.
*
* This function deinitializes the SD card by setting the STA_NOINIT bit in the
* status field and setting the card type to SDCARD_NONE. It also deinitializes
* the chip select GPIO.
*
* @param sd_card_p Pointer to the sd_card_t structure to be deinitialized.
*/
static void sd_deinit(sd_card_t *sd_card_p) {
sd_card_p->state.m_Status |= STA_NOINIT;
sd_card_p->state.card_type = SDCARD_NONE;
if ((uint)-1 != sd_card_p->spi_if_p->ss_gpio) {
gpio_deinit(sd_card_p->spi_if_p->ss_gpio);
gpio_set_dir(sd_card_p->spi_if_p->ss_gpio, GPIO_IN);
}
}
/**
* @brief Initializes the sd_card_t structure.
*
* This function initializes the sd_card_t structure with pointers to the
* respective functions for reading and writing blocks, syncing the card,
* initializing the card, deinitializing the card, getting the number of
* sectors, and testing the card's communication. It also initializes the chip
* select GPIO pin.
*
* @param sd_card_p Pointer to the sd_card_t structure to be initialized.
*/
void sd_spi_ctor(sd_card_t *sd_card_p) {
sd_card_p->write_blocks = sd_write_blocks;
sd_card_p->read_blocks = sd_read_blocks;
sd_card_p->sync = sd_sync;
sd_card_p->init = sd_card_spi_init;
sd_card_p->deinit = sd_deinit;
sd_card_p->get_num_sectors = sd_spi_sectors;
sd_card_p->sd_test_com = sd_spi_test_com;
// Chip select is active-low, so we'll initialise it to a
// driven-high state.
if ((uint)-1 == sd_card_p->spi_if_p->ss_gpio) return;
gpio_init(sd_card_p->spi_if_p->ss_gpio);
gpio_put(sd_card_p->spi_if_p->ss_gpio, 1); // Avoid any glitches when enabling output
gpio_set_dir(sd_card_p->spi_if_p->ss_gpio, GPIO_OUT);
gpio_put(sd_card_p->spi_if_p->ss_gpio, 1); // In case set_dir does anything
if (sd_card_p->spi_if_p->set_drive_strength) {
gpio_set_drive_strength(sd_card_p->spi_if_p->ss_gpio,
sd_card_p->spi_if_p->ss_gpio_drive_strength);
}
}
/* [] END OF FILE */
/* sd_card_spi.h
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#pragma once
#include "sd_card.h"
#ifdef __cplusplus
extern "C" {
#endif
void sd_spi_ctor(sd_card_t *sd_card_p); // Constructor for sd_card_t
uint32_t sd_go_idle_state(sd_card_t *sd_card_p);
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */
/* sd_spi.c
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
/* Standard includes. */
#include <inttypes.h>
#include <stdint.h>
#include <string.h>
//
#include "hardware/gpio.h"
//
#include "my_debug.h"
#include "delays.h"
#include "my_spi.h"
//
#if !defined(USE_DBG_PRINTF) || defined(NDEBUG)
# pragma GCC diagnostic ignored "-Wunused-variable"
#endif
//
#include "sd_spi.h"
// #define TRACE_PRINTF(fmt, args...)
// #define TRACE_PRINTF printf
void sd_spi_go_high_frequency(sd_card_t *sd_card_p) {
uint actual = spi_set_baudrate(sd_card_p->spi_if_p->spi->hw_inst, sd_card_p->spi_if_p->spi->baud_rate);
DBG_PRINTF("%s: Actual frequency: %lu\n", __FUNCTION__, (long)actual);
}
void sd_spi_go_low_frequency(sd_card_t *sd_card_p) {
uint actual = spi_set_baudrate(sd_card_p->spi_if_p->spi->hw_inst, 400 * 1000); // Actual frequency: 398089
DBG_PRINTF("%s: Actual frequency: %lu\n", __FUNCTION__, (long)actual);
}
/*
After power up, the host starts the clock and sends the initializing sequence on the CMD line.
This sequence is a contiguous stream of logical ‘1’s. The sequence length is the maximum of 1msec,
74 clocks or the supply-ramp-uptime; the additional 10 clocks
(over the 64 clocks after what the card should be ready for communication) is
provided to eliminate power-up synchronization problems.
*/
void sd_spi_send_initializing_sequence(sd_card_t *sd_card_p) {
if ((uint)-1 == sd_card_p->spi_if_p->ss_gpio) return;
bool old_ss = gpio_get(sd_card_p->spi_if_p->ss_gpio);
// Set DI and CS high and apply 74 or more clock pulses to SCLK:
gpio_put(sd_card_p->spi_if_p->ss_gpio, 1);
uint8_t ones[10];
memset(ones, 0xFF, sizeof ones);
uint32_t start = millis();
do {
spi_transfer(sd_card_p->spi_if_p->spi, ones, NULL, sizeof ones);
} while (millis() - start < 1);
gpio_put(sd_card_p->spi_if_p->ss_gpio, old_ss);
}
/* [] END OF FILE */
/* sd_spi.h
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#pragma once
#include <stdint.h>
//
#include "pico/stdlib.h"
//
#include "delays.h"
#include "my_debug.h"
#include "my_spi.h"
#include "sd_card.h"
#include "sd_timeouts.h"
#ifdef NDEBUG
#pragma GCC diagnostic ignored "-Wunused-variable"
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* Transfer tx to SPI while receiving SPI to rx.
tx or rx can be NULL if not important. */
void sd_spi_go_low_frequency(sd_card_t *this);
void sd_spi_go_high_frequency(sd_card_t *this);
/*
After power up, the host starts the clock and sends the initializing sequence on the CMD line.
This sequence is a contiguous stream of logical ‘1’s. The sequence length is the maximum of
1msec, 74 clocks or the supply-ramp-uptime; the additional 10 clocks (over the 64 clocks after
what the card should be ready for communication) is provided to eliminate power-up
synchronization problems.
*/
void sd_spi_send_initializing_sequence(sd_card_t *sd_card_p);
static inline uint8_t sd_spi_read(sd_card_t *sd_card_p) {
uint8_t received = SPI_FILL_CHAR;
uint32_t start = millis();
while (!spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst) &&
millis() - start < sd_timeouts.sd_spi_read)
tight_loop_contents();
myASSERT(spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst));
int num = spi_read_blocking(sd_card_p->spi_if_p->spi->hw_inst, SPI_FILL_CHAR, &received, 1);
myASSERT(1 == num);
return received;
}
static inline void sd_spi_write(sd_card_t *sd_card_p, const uint8_t value) {
uint32_t start = millis();
while (!spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst) &&
millis() - start < sd_timeouts.sd_spi_write)
tight_loop_contents();
myASSERT(spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst));
int num = spi_write_blocking(sd_card_p->spi_if_p->spi->hw_inst, &value, 1);
myASSERT(1 == num);
}
static inline uint8_t sd_spi_write_read(sd_card_t *sd_card_p, const uint8_t value) {
uint8_t received = SPI_FILL_CHAR;
uint32_t start = millis();
while (!spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst) &&
millis() - start < sd_timeouts.sd_spi_write_read)
tight_loop_contents();
myASSERT(spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst));
int num = spi_write_read_blocking(sd_card_p->spi_if_p->spi->hw_inst, &value, &received, 1);
myASSERT(1 == num);
return received;
}
// Would do nothing if sd_card_p->spi_if_p->ss_gpio were set to GPIO_FUNC_SPI.
static inline void sd_spi_select(sd_card_t *sd_card_p) {
if ((uint)-1 == sd_card_p->spi_if_p->ss_gpio) return;
gpio_put(sd_card_p->spi_if_p->ss_gpio, 0);
// See http://elm-chan.org/docs/mmc/mmc_e.html#spibus
sd_spi_write(sd_card_p, SPI_FILL_CHAR);
LED_ON();
}
static inline void sd_spi_deselect(sd_card_t *sd_card_p) {
if ((uint)-1 == sd_card_p->spi_if_p->ss_gpio) return;
gpio_put(sd_card_p->spi_if_p->ss_gpio, 1);
LED_OFF();
/*
MMC/SDC enables/disables the DO output in synchronising to the SCLK. This
means there is a posibility of bus conflict with MMC/SDC and another SPI
slave that shares an SPI bus. Therefore to make MMC/SDC release the MISO
line, the master device needs to send a byte after the CS signal is
deasserted.
*/
sd_spi_write(sd_card_p, SPI_FILL_CHAR);
}
static inline void sd_spi_lock(sd_card_t *sd_card_p) { spi_lock(sd_card_p->spi_if_p->spi); }
static inline void sd_spi_unlock(sd_card_t *sd_card_p) { spi_unlock(sd_card_p->spi_if_p->spi); }
static inline void sd_spi_acquire(sd_card_t *sd_card_p) {
sd_spi_lock(sd_card_p);
sd_spi_select(sd_card_p);
}
static inline void sd_spi_release(sd_card_t *sd_card_p) {
sd_spi_deselect(sd_card_p);
sd_spi_unlock(sd_card_p);
}
/* Transfer tx to SPI while receiving SPI to rx.
tx or rx can be NULL if not important. */
static inline void sd_spi_transfer_start(sd_card_t *sd_card_p, const uint8_t *tx, uint8_t *rx,
size_t length) {
return spi_transfer_start(sd_card_p->spi_if_p->spi, tx, rx, length);
}
static inline bool sd_spi_transfer_wait_complete(sd_card_t *sd_card_p, uint32_t timeout_ms) {
return spi_transfer_wait_complete(sd_card_p->spi_if_p->spi, timeout_ms);
}
static inline bool sd_spi_transfer(sd_card_t *sd_card_p, const uint8_t *tx, uint8_t *rx,
size_t length) {
return spi_transfer(sd_card_p->spi_if_p->spi, tx, rx, length);
}
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */
#include "hw_config.h"
#include "sd_card.h"
//
#include "dma_interrupts.h"
static void dma_irq_handler(const uint DMA_IRQ_num, io_rw_32 *dma_hw_ints_p) {
// Iterate through all of the SD cards
for (size_t i = 0; i < sd_get_num(); ++i) {
sd_card_t *sd_card_p = sd_get_by_num(i);
if (!sd_card_p)
continue;
uint irq_num = 0, channel = 0;
if (SD_IF_SDIO == sd_card_p->type) {
irq_num = sd_card_p->sdio_if_p->DMA_IRQ_num;
channel = sd_card_p->sdio_if_p->state.SDIO_DMA_CHB;
}
// Is this channel requesting interrupt?
if (irq_num == DMA_IRQ_num && (*dma_hw_ints_p & (1 << channel))) {
*dma_hw_ints_p = 1 << channel; // Clear it.
if (SD_IF_SDIO == sd_card_p->type) {
sdio_irq_handler(sd_card_p);
}
}
}
}
static void __not_in_flash_func(dma_irq_handler_0)() {
dma_irq_handler(DMA_IRQ_0, &dma_hw->ints0);
}
static void __not_in_flash_func(dma_irq_handler_1)() {
dma_irq_handler(DMA_IRQ_1, &dma_hw->ints1);
}
/* Adding the interrupt request handler
Only add it once.
Otherwise, space is wasted in irq_add_shared_handler's table.
Also, each core maintains its own table of interrupt vectors,
and we don't want both cores to call the IRQ handler.
*/
typedef struct ih_added_rec_t {
uint num;
bool added;
} ih_added_rec_t;
static ih_added_rec_t ih_added_recs[] = {
{DMA_IRQ_0, false},
{DMA_IRQ_1, false}};
static bool is_handler_added(const uint num) {
for (size_t i = 0; i < count_of(ih_added_recs); ++i)
if (num == ih_added_recs[i].num)
return ih_added_recs[i].added;
return false;
}
static void mark_handler_added(const uint num) {
size_t i;
for (i = 0; i < count_of(ih_added_recs); ++i)
if (num == ih_added_recs[i].num) {
ih_added_recs[i].added = true;
break;
}
myASSERT(i < count_of(ih_added_recs));
}
void dma_irq_add_handler(const uint num, bool exclusive) {
if (!is_handler_added(num)) {
static void (*irq_handler)();
switch (num) {
case DMA_IRQ_0:
irq_handler = dma_irq_handler_0;
break;
case DMA_IRQ_1:
irq_handler = dma_irq_handler_1;
break;
default:
myASSERT(false);
}
if (exclusive) {
irq_set_exclusive_handler(num, *irq_handler);
} else {
irq_add_shared_handler(
num, *irq_handler,
PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
}
irq_set_enabled(num, true); // Enable IRQ in NVIC
mark_handler_added(num);
}
}
#pragma once
#include "pico.h"
#ifdef __cplusplus
extern "C" {
#endif
void dma_irq_add_handler(const uint num, bool exclusive);
#ifdef __cplusplus
}
#endif
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
#define CLKDIV 4
#define SDIO_CLK_PIN_D0_OFFSET 30
// ------------ //
// sdio_cmd_clk //
// ------------ //
#define sdio_cmd_clk_wrap_target 0
#define sdio_cmd_clk_wrap 17
static const uint16_t sdio_cmd_clk_program_instructions[] = {
// .wrap_target
0xb1e3, // 0: mov osr, null side 1 [1]
0xa14d, // 1: mov y, !status side 0 [1]
0x1161, // 2: jmp !y, 1 side 1 [1]
0x6160, // 3: out null, 32 side 0 [1]
0x7128, // 4: out x, 8 side 1 [1]
0xe101, // 5: set pins, 1 side 0 [1]
0xf181, // 6: set pindirs, 1 side 1 [1]
0x6101, // 7: out pins, 1 side 0 [1]
0x1147, // 8: jmp x--, 7 side 1 [1]
0xe180, // 9: set pindirs, 0 side 0 [1]
0x7128, // 10: out x, 8 side 1 [1]
0xa142, // 11: nop side 0 [1]
0x1131, // 12: jmp !x, 17 side 1 [1]
0xa142, // 13: nop side 0 [1]
0x11cd, // 14: jmp pin, 13 side 1 [1]
0x4101, // 15: in pins, 1 side 0 [1]
0x114f, // 16: jmp x--, 15 side 1 [1]
0x8120, // 17: push block side 0 [1]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program sdio_cmd_clk_program = {
.instructions = sdio_cmd_clk_program_instructions,
.length = 18,
.origin = -1,
};
static inline pio_sm_config sdio_cmd_clk_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + sdio_cmd_clk_wrap_target, offset + sdio_cmd_clk_wrap);
sm_config_set_sideset(&c, 1, false, false);
return c;
}
#endif
// ------------ //
// sdio_data_rx //
// ------------ //
#define sdio_data_rx_wrap_target 0
#define sdio_data_rx_wrap 4
static const uint16_t sdio_data_rx_program_instructions[] = {
// .wrap_target
0xa022, // 0: mov x, y
0x2020, // 1: wait 0 pin, 0
0x23be, // 2: wait 1 pin, 30 [3]
0x4204, // 3: in pins, 4 [2]
0x0043, // 4: jmp x--, 3
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program sdio_data_rx_program = {
.instructions = sdio_data_rx_program_instructions,
.length = 5,
.origin = -1,
};
static inline pio_sm_config sdio_data_rx_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + sdio_data_rx_wrap_target, offset + sdio_data_rx_wrap);
return c;
}
#endif
// ------------ //
// sdio_data_tx //
// ------------ //
#define sdio_data_tx_wrap_target 5
#define sdio_data_tx_wrap 8
static const uint16_t sdio_data_tx_program_instructions[] = {
0x203e, // 0: wait 0 pin, 30
0x24be, // 1: wait 1 pin, 30 [4]
0x6104, // 2: out pins, 4 [1]
0x0142, // 3: jmp x--, 2 [1]
0xe180, // 4: set pindirs, 0 [1]
// .wrap_target
0x4101, // 5: in pins, 1 [1]
0x0185, // 6: jmp y--, 5 [1]
0x21a0, // 7: wait 1 pin, 0 [1]
0x8120, // 8: push block [1]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program sdio_data_tx_program = {
.instructions = sdio_data_tx_program_instructions,
.length = 9,
.origin = -1,
};
static inline pio_sm_config sdio_data_tx_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + sdio_data_tx_wrap_target, offset + sdio_data_tx_wrap);
return c;
}
#endif
/* sd_card.c
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
/* Standard includes. */
#include <stdlib.h>
#include <ctype.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
//
#include "pico/mutex.h"
//
#include "SDIO/SdioCard.h"
#include "SPI/sd_card_spi.h"
#include "hw_config.h" // Hardware Configuration of the SPI and SD Card "objects"
#include "my_debug.h"
#include "sd_card_constants.h"
#include "sd_regs.h"
#include "sd_timeouts.h"
#include "util.h"
//
#include "diskio.h" /* Declarations of disk functions */ // Needed for STA_NOINIT, ...
//
#include "sd_card.h"
#define TRACE_PRINTF(fmt, args...)
// #define TRACE_PRINTF printf
#ifdef NDEBUG
# pragma GCC diagnostic ignored "-Wunused-variable"
#endif
static bool driver_initialized;
// An SD card can only do one thing at a time.
void sd_lock(sd_card_t *sd_card_p) {
myASSERT(mutex_is_initialized(&sd_card_p->state.mutex));
mutex_enter_blocking(&sd_card_p->state.mutex);
}
void sd_unlock(sd_card_t *sd_card_p) {
myASSERT(mutex_is_initialized(&sd_card_p->state.mutex));
mutex_exit(&sd_card_p->state.mutex);
}
bool sd_is_locked(sd_card_t *sd_card_p) {
myASSERT(mutex_is_initialized(&sd_card_p->state.mutex));
uint32_t owner_out;
return !mutex_try_enter(&sd_card_p->state.mutex, &owner_out);
}
sd_card_t *sd_get_by_drive_prefix(const char *const drive_prefix) {
// Numeric drive number is always valid
if (2 == strlen(drive_prefix) && isdigit((unsigned char)drive_prefix[0]) &&
':' == drive_prefix[1])
return sd_get_by_num(atoi(drive_prefix));
#if FF_STR_VOLUME_ID
for (size_t i = 0; i < sd_get_num(); ++i) {
// Ignore '/', trailing ':'
if (strstr(drive_prefix, VolumeStr[i])) return sd_get_by_num(i);
}
EMSG_PRINTF("%s: unknown drive prefix %s\n", __func__, drive_prefix);
#endif
return NULL;
}
/* Return non-zero if the SD-card is present. */
bool sd_card_detect(sd_card_t *sd_card_p) {
TRACE_PRINTF("> %s\r\n", __FUNCTION__);
if (!sd_card_p->use_card_detect) {
sd_card_p->state.m_Status &= ~STA_NODISK;
return true;
}
/*!< Check GPIO to detect SD */
if (gpio_get(sd_card_p->card_detect_gpio) == sd_card_p->card_detected_true) {
// The socket is now occupied
sd_card_p->state.m_Status &= ~STA_NODISK;
TRACE_PRINTF("SD card detected!\r\n");
return true;
} else {
// The socket is now empty
sd_card_p->state.m_Status |= (STA_NODISK | STA_NOINIT);
sd_card_p->state.card_type = SDCARD_NONE;
EMSG_PRINTF("No SD card detected!\r\n");
return false;
}
}
void sd_set_drive_prefix(sd_card_t *sd_card_p, size_t phy_drv_num) {
#if FF_STR_VOLUME_ID == 0
int rc = snprintf(sd_card_p->state.drive_prefix, sizeof sd_card_p->state.drive_prefix,
"%d:", phy_drv_num);
#elif FF_STR_VOLUME_ID == 1 /* Arbitrary string is enabled */
// Add ':'
int rc = snprintf(sd_card_p->state.drive_prefix, sizeof sd_card_p->state.drive_prefix,
"%s:", VolumeStr[phy_drv_num]);
#elif FF_STR_VOLUME_ID == 2 /* Unix style drive prefix */
// Add '/'
int rc = snprintf(sd_card_p->state.drive_prefix, sizeof sd_card_p->state.drive_prefix,
"/%s", VolumeStr[phy_drv_num]);
#else
#error "Unknown FF_STR_VOLUME_ID"
#endif
// Notice that only when this returned value is non-negative and less than n,
// the string has been completely written.
myASSERT(0 <= rc && (size_t)rc < sizeof sd_card_p->state.drive_prefix);
}
char const *sd_get_drive_prefix(sd_card_t *sd_card_p) {
myASSERT(driver_initialized);
myASSERT(sd_card_p);
if (!sd_card_p) return "";
return sd_card_p->state.drive_prefix;
}
bool sd_init_driver() {
auto_init_mutex(initialized_mutex);
mutex_enter_blocking(&initialized_mutex);
bool ok = true;
if (!driver_initialized) {
myASSERT(sd_get_num());
for (size_t i = 0; i < sd_get_num(); ++i) {
sd_card_t *sd_card_p = sd_get_by_num(i);
if (!sd_card_p) continue;
myASSERT(sd_card_p->type);
if (!mutex_is_initialized(&sd_card_p->state.mutex))
mutex_init(&sd_card_p->state.mutex);
sd_lock(sd_card_p);
sd_card_p->state.m_Status = STA_NOINIT;
sd_set_drive_prefix(sd_card_p, i);
// Set up Card Detect
if (sd_card_p->use_card_detect) {
if (sd_card_p->card_detect_use_pull) {
if (sd_card_p->card_detect_pull_hi) {
gpio_pull_up(sd_card_p->card_detect_gpio);
} else {
gpio_pull_down(sd_card_p->card_detect_gpio);
}
}
gpio_init(sd_card_p->card_detect_gpio);
}
switch (sd_card_p->type) {
case SD_IF_NONE:
myASSERT(false);
break;
case SD_IF_SPI:
myASSERT(sd_card_p->spi_if_p); // Must have an interface object
myASSERT(sd_card_p->spi_if_p->spi);
sd_spi_ctor(sd_card_p);
if (!my_spi_init(sd_card_p->spi_if_p->spi)) {
ok = false;
}
/* At power up the SD card CD/DAT3 / CS line has a 50KOhm pull up enabled
* in the card. This resistor serves two functions Card detection and Mode
* Selection. For Mode Selection, the host can drive the line high or let it
* be pulled high to select SD mode. If the host wants to select SPI mode it
* should drive the line low.
*
* There is an important thing needs to be considered that the MMC/SDC is
* initially NOT the SPI device. Some bus activity to access another SPI
* device can cause a bus conflict due to an accidental response of the
* MMC/SDC. Therefore the MMC/SDC should be initialized to put it into the
* SPI mode prior to access any other device attached to the same SPI bus.
*/
sd_go_idle_state(sd_card_p);
break;
case SD_IF_SDIO:
myASSERT(sd_card_p->sdio_if_p);
sd_sdio_ctor(sd_card_p);
break;
default:
myASSERT(false);
} // switch (sd_card_p->type)
sd_unlock(sd_card_p);
} // for
driver_initialized = true;
}
mutex_exit(&initialized_mutex);
return ok;
}
void cidDmp(sd_card_t *sd_card_p, printer_t printer) {
// +-----------------------+-------+-------+-----------+
// | Name | Field | Width | CID-slice |
// +-----------------------+-------+-------+-----------+
// | Manufacturer ID | MID | 8 | [127:120] | 15
(*printer)(
"\nManufacturer ID: "
"0x%x\n",
ext_bits16(sd_card_p->state.CID, 127, 120));
// | OEM/Application ID | OID | 16 | [119:104] | 14
(*printer)("OEM ID: ");
{
char buf[3];
ext_str(16, sd_card_p->state.CID, 119, 104, sizeof buf, buf);
(*printer)("%s", buf);
}
// | Product name | PNM | 40 | [103:64] | 12
(*printer)("Product: ");
{
char buf[6];
ext_str(16, sd_card_p->state.CID, 103, 64, sizeof buf, buf);
(*printer)("%s", buf);
}
// | Product revision | PRV | 8 | [63:56] | 7
(*printer)(
"\nRevision: "
"%d.%d\n",
ext_bits16(sd_card_p->state.CID, 63, 60), ext_bits16(sd_card_p->state.CID, 59, 56));
// | Product serial number | PSN | 32 | [55:24] | 6
// (*printer)("0x%lx\n", __builtin_bswap32(ext_bits16(sd_card_p->state.CID, 55, 24))
(*printer)(
"Serial number: "
"0x%lx\n",
ext_bits16(sd_card_p->state.CID, 55, 24));
// | reserved | -- | 4 | [23:20] | 2
// | Manufacturing date | MDT | 12 | [19:8] |
// The "m" field [11:8] is the month code. 1 = January.
// The "y" field [19:12] is the year code. 0 = 2000.
(*printer)(
"Manufacturing date: "
"%d/%d\n",
ext_bits16(sd_card_p->state.CID, 11, 8),
ext_bits16(sd_card_p->state.CID, 19, 12) + 2000);
(*printer)("\n");
// | CRC7 checksum | CRC | 7 | [7:1] | 0
// | not used, always 1- | 1 | [0:0] | |
// +-----------------------+-------+-------+-----------+
}
void csdDmp(sd_card_t *sd_card_p, printer_t printer) {
uint32_t c_size, c_size_mult, read_bl_len;
uint32_t block_len, mult, blocknr;
uint32_t hc_c_size;
uint64_t blocks = 0, capacity = 0;
bool erase_single_block_enable = 0;
uint8_t erase_sector_size = 0;
// csd_structure : CSD[127:126]
int csd_structure = ext_bits16(sd_card_p->state.CSD, 127, 126);
switch (csd_structure) {
case 0:
c_size = ext_bits16(sd_card_p->state.CSD, 73, 62); // c_size : CSD[73:62]
c_size_mult =
ext_bits16(sd_card_p->state.CSD, 49, 47); // c_size_mult : CSD[49:47]
read_bl_len =
ext_bits16(sd_card_p->state.CSD, 83, 80); // read_bl_len : CSD[83:80] - the
// *maximum* read block length
block_len = 1 << read_bl_len; // BLOCK_LEN = 2^READ_BL_LEN
mult = 1 << (c_size_mult + 2); // MULT = 2^C_SIZE_MULT+2 (C_SIZE_MULT < 8)
blocknr = (c_size + 1) * mult; // BLOCKNR = (C_SIZE+1) * MULT
capacity = (uint64_t)blocknr * block_len; // memory capacity = BLOCKNR * BLOCK_LEN
blocks = capacity / sd_block_size;
(*printer)("Standard Capacity: c_size: %" PRIu32 "\r\n", c_size);
(*printer)("Sectors: 0x%llx : %llu\r\n", blocks, blocks);
(*printer)("Capacity: 0x%llx : %llu MiB\r\n", capacity,
(capacity / (1024U * 1024U)));
break;
case 1:
hc_c_size =
ext_bits16(sd_card_p->state.CSD, 69, 48); // device size : C_SIZE : [69:48]
blocks = (hc_c_size + 1) << 10; // block count = C_SIZE+1) * 1K
// byte (512B is block size)
/* ERASE_BLK_EN
The ERASE_BLK_EN defines the granularity of the unit size of the data to be erased.
The erase operation can erase either one or multiple units of 512 bytes or one or
multiple units (or sectors) of SECTOR_SIZE. If ERASE_BLK_EN=0, the host can erase
one or multiple units of SECTOR_SIZE. If ERASE_BLK_EN=1 the host can erase one or
multiple units of 512 bytes.
*/
erase_single_block_enable = ext_bits16(sd_card_p->state.CSD, 46, 46);
/* SECTOR_SIZE
The size of an erasable sector. The content of this register is a 7-bit binary coded
value, defining the number of write blocks. The actual size is computed by
increasing this number by one. A value of zero means one write block, 127 means 128
write blocks.
*/
erase_sector_size = ext_bits16(sd_card_p->state.CSD, 45, 39) + 1;
(*printer)("SDHC/SDXC Card: hc_c_size: %" PRIu32 "\r\n", hc_c_size);
(*printer)("Sectors: %llu\r\n", blocks);
(*printer)("Capacity: %llu MiB (%llu MB)\r\n", blocks / 2048,
blocks * sd_block_size / 1000000);
(*printer)("ERASE_BLK_EN: %s\r\n", erase_single_block_enable
? "units of 512 bytes"
: "units of SECTOR_SIZE");
(*printer)("SECTOR_SIZE (size of an erasable sector): %d (%lu bytes)\r\n",
erase_sector_size,
(uint32_t)(erase_sector_size ? 512 : 1) * erase_sector_size);
break;
default:
(*printer)("CSD struct unsupported\r\n");
};
}
#define KB 1024
#define MB (1024 * 1024)
/* AU (Allocation Unit):
is a physical boundary of the card and consists of one or more blocks and its
size depends on each card. */
bool sd_allocation_unit(sd_card_t *sd_card_p, size_t *au_size_bytes_p) {
if (SD_IF_SPI == sd_card_p->type) return false; // SPI can't do full SD Status
uint8_t status[64] = {0};
bool ok = rp2040_sdio_get_sd_status(sd_card_p, status);
if (!ok) return false;
// 431:428 AU_SIZE
uint8_t au_size = ext_bits(64, status, 431, 428);
switch (au_size) {
// AU_SIZE Value Definition
case 0x0:
*au_size_bytes_p = 0;
break; // Not Defined
case 0x1:
*au_size_bytes_p = 16 * KB;
break;
case 0x2:
*au_size_bytes_p = 32 * KB;
break;
case 0x3:
*au_size_bytes_p = 64 * KB;
break;
case 0x4:
*au_size_bytes_p = 128 * KB;
break;
case 0x5:
*au_size_bytes_p = 256 * KB;
break;
case 0x6:
*au_size_bytes_p = 512 * KB;
break;
case 0x7:
*au_size_bytes_p = 1 * MB;
break;
case 0x8:
*au_size_bytes_p = 2 * MB;
break;
case 0x9:
*au_size_bytes_p = 4 * MB;
break;
case 0xA:
*au_size_bytes_p = 8 * MB;
break;
case 0xB:
*au_size_bytes_p = 12 * MB;
break;
case 0xC:
*au_size_bytes_p = 16 * MB;
break;
case 0xD:
*au_size_bytes_p = 24 * MB;
break;
case 0xE:
*au_size_bytes_p = 32 * MB;
break;
case 0xF:
*au_size_bytes_p = 64 * MB;
break;
default:
myASSERT(false);
}
return true;
}
/* [] END OF FILE */
/* sd_card.h
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
// Note: The model used here is one FatFS per SD card.
// Multiple partitions on a card are not supported.
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
//
#include <hardware/pio.h>
#include "hardware/gpio.h"
#include "pico/mutex.h"
//
#include "ff.h"
//
#include "SDIO/rp2040_sdio.h"
#include "SPI/my_spi.h"
#include "SPI/sd_card_spi.h"
#include "diskio.h"
#include "sd_card_constants.h"
#include "sd_regs.h"
#include "util.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum { SD_IF_NONE, SD_IF_SPI, SD_IF_SDIO } sd_if_t;
typedef struct sd_spi_if_state_t {
bool ongoing_mlt_blk_wrt;
uint32_t cont_sector_wrt;
uint32_t n_wrt_blks_reqd;
} sd_spi_if_state_t;
typedef struct sd_spi_if_t {
spi_t *spi;
// Slave select is here instead of in spi_t because multiple SDs can share an SPI.
uint ss_gpio; // Slave select for this SD card
// Drive strength levels for GPIO outputs:
// GPIO_DRIVE_STRENGTH_2MA
// GPIO_DRIVE_STRENGTH_4MA
// GPIO_DRIVE_STRENGTH_8MA
// GPIO_DRIVE_STRENGTH_12MA
bool set_drive_strength;
enum gpio_drive_strength ss_gpio_drive_strength;
sd_spi_if_state_t state;
} sd_spi_if_t;
typedef struct sd_sdio_if_t {
// See sd_driver\SDIO\rp2040_sdio.pio for SDIO_CLK_PIN_D0_OFFSET
uint CLK_gpio; // Must be (D0_gpio + SDIO_CLK_PIN_D0_OFFSET) % 32
uint CMD_gpio;
uint D0_gpio; // D0
uint D1_gpio; // Must be D0 + 1
uint D2_gpio; // Must be D0 + 2
uint D3_gpio; // Must be D0 + 3
PIO SDIO_PIO; // either pio0 or pio1
uint DMA_IRQ_num; // DMA_IRQ_0 or DMA_IRQ_1
bool use_exclusive_DMA_IRQ_handler;
uint baud_rate;
// Drive strength levels for GPIO outputs:
// GPIO_DRIVE_STRENGTH_2MA
// GPIO_DRIVE_STRENGTH_4MA
// GPIO_DRIVE_STRENGTH_8MA
// GPIO_DRIVE_STRENGTH_12MA
bool set_drive_strength;
enum gpio_drive_strength CLK_gpio_drive_strength;
enum gpio_drive_strength CMD_gpio_drive_strength;
enum gpio_drive_strength D0_gpio_drive_strength;
enum gpio_drive_strength D1_gpio_drive_strength;
enum gpio_drive_strength D2_gpio_drive_strength;
enum gpio_drive_strength D3_gpio_drive_strength;
/* The following fields are not part of the configuration.
They are state variables, and are dynamically assigned. */
sd_sdio_if_state_t state;
} sd_sdio_if_t;
typedef struct sd_card_state_t {
DSTATUS m_Status; // Card status
card_type_t card_type; // Assigned dynamically
CSD_t CSD; // Card-Specific Data register.
CID_t CID; // Card IDentification register
uint32_t sectors; // Assigned dynamically
mutex_t mutex;
FATFS fatfs;
bool mounted;
#if FF_STR_VOLUME_ID
char drive_prefix[32];
#else
char drive_prefix[4];
#endif
} sd_card_state_t;
typedef struct sd_card_t sd_card_t;
// "Class" representing SD Cards
struct sd_card_t {
sd_if_t type; // Interface type
union {
sd_spi_if_t *spi_if_p;
sd_sdio_if_t *sdio_if_p;
};
bool use_card_detect;
uint card_detect_gpio; // Card detect; ignored if !use_card_detect
uint card_detected_true; // Varies with card socket; ignored if !use_card_detect
bool card_detect_use_pull;
bool card_detect_pull_hi;
/* The following fields are state variables and not part of the configuration.
They are dynamically assigned. */
sd_card_state_t state;
DSTATUS (*init)(sd_card_t *sd_card_p);
void (*deinit)(sd_card_t *sd_card_p);
block_dev_err_t (*write_blocks)(sd_card_t *sd_card_p, const uint8_t *buffer,
uint32_t ulSectorNumber, uint32_t blockCnt);
block_dev_err_t (*read_blocks)(sd_card_t *sd_card_p, uint8_t *buffer,
uint32_t ulSectorNumber, uint32_t ulSectorCount);
block_dev_err_t (*sync)(sd_card_t *sd_card_p);
uint32_t (*get_num_sectors)(sd_card_t *sd_card_p);
// Useful when use_card_detect is false - call periodically to check for presence of SD card
// Returns true if and only if SD card was sensed on the bus
bool (*sd_test_com)(sd_card_t *sd_card_p);
};
void sd_lock(sd_card_t *sd_card_p);
void sd_unlock(sd_card_t *sd_card_p);
bool sd_is_locked(sd_card_t *sd_card_p);
bool sd_init_driver();
bool sd_card_detect(sd_card_t *sd_card_p);
void cidDmp(sd_card_t *sd_card_p, printer_t printer);
void csdDmp(sd_card_t *sd_card_p, printer_t printer);
bool sd_allocation_unit(sd_card_t *sd_card_p, size_t *au_size_bytes_p);
sd_card_t *sd_get_by_drive_prefix(const char *const name);
// sd_init_driver() must be called before this:
char const *sd_get_drive_prefix(sd_card_t *sd_card_p);
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */
/* sd_card_constants.h
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/*!< Block size supported for SD card is 512 bytes */
// Only HC block size is supported.
static const size_t sd_block_size = 512;
typedef enum {
SD_BLOCK_DEVICE_ERROR_NONE = 0,
SD_BLOCK_DEVICE_ERROR_WOULD_BLOCK = 1 << 0, /*!< operation would block */
SD_BLOCK_DEVICE_ERROR_UNSUPPORTED = 1 << 1, /*!< unsupported operation */
SD_BLOCK_DEVICE_ERROR_PARAMETER = 1 << 2, /*!< invalid parameter */
SD_BLOCK_DEVICE_ERROR_NO_INIT = 1 << 3, /*!< uninitialized */
SD_BLOCK_DEVICE_ERROR_NO_DEVICE = 1 << 4, /*!< device is missing or not connected */
SD_BLOCK_DEVICE_ERROR_WRITE_PROTECTED = 1 << 5, /*!< write protected */
SD_BLOCK_DEVICE_ERROR_UNUSABLE = 1 << 6, /*!< unusable card */
SD_BLOCK_DEVICE_ERROR_NO_RESPONSE = 1 << 7, /*!< No response from device */
SD_BLOCK_DEVICE_ERROR_CRC = 1 << 8, /*!< CRC error */
SD_BLOCK_DEVICE_ERROR_ERASE = 1 << 9, /*!< Erase error: reset/sequence */
SD_BLOCK_DEVICE_ERROR_WRITE = 1 << 10 /*!< Write error: !SPI_DATA_ACCEPTED */
} block_dev_err_t;
/** Represents the different SD/MMC card types */
typedef enum {
SDCARD_NONE = 0, /**< No card is present */
SDCARD_V1 = 1, /**< v1.x Standard Capacity */
SDCARD_V2 = 2, /**< v2.x Standard capacity SD card */
SDCARD_V2HC = 3, /**< v2.x High capacity SD card */
CARD_UNKNOWN = 4 /**< Unknown or unsupported card */
} card_type_t;
/* On the wire, convert to hex and add 0x40 for transmitter bit.
e.g.: CMD17_READ_SINGLE_BLOCK: 17 = 0x11; 0x11 | 0x40 = 0x51 */
// Supported SD Card Commands
typedef enum { /* Number on wire in parens */
CMD_NOT_SUPPORTED = -1, /* Command not supported error */
CMD0_GO_IDLE_STATE = 0, /* Resets the SD Memory Card */
CMD1_SEND_OP_COND = 1, /* Sends host capacity support */
CMD2_ALL_SEND_CID = 2, /* Asks any card to send the CID. */
CMD3_SEND_RELATIVE_ADDR = 3, /* Ask the card to publish a new RCA. */
CMD6_SWITCH_FUNC = 6, /* Check and Switches card function */
CMD7_SELECT_CARD = 7, /* SELECT/DESELECT_CARD - toggles between the stand-by and transfer states. */
CMD8_SEND_IF_COND = 8, /* Supply voltage info */
CMD9_SEND_CSD = 9, /* Provides Card Specific data */
CMD10_SEND_CID = 10, /* Provides Card Identification */
CMD12_STOP_TRANSMISSION = 12, /* Forces the card to stop transmission */
CMD13_SEND_STATUS = 13, /* (0x4D) Card responds with status */
CMD16_SET_BLOCKLEN = 16, /* Length for SC card is set */
CMD17_READ_SINGLE_BLOCK = 17, /* (0x51) Read single block of data */
CMD18_READ_MULTIPLE_BLOCK = 18, /* (0x52) Continuously Card transfers data blocks to host
until interrupted by a STOP_TRANSMISSION command */
CMD24_WRITE_BLOCK = 24, /* (0x58) Write single block of data */
CMD25_WRITE_MULTIPLE_BLOCK = 25, /* (0x59) Continuously writes blocks of data
until 'Stop Tran' token is sent */
CMD27_PROGRAM_CSD = 27, /* Programming bits of CSD */
CMD32_ERASE_WR_BLK_START_ADDR = 32, /* Sets the address of the first write
block to be erased. */
CMD33_ERASE_WR_BLK_END_ADDR = 33, /* Sets the address of the last write
block of the continuous range to be erased.*/
CMD38_ERASE = 38, /* Erases all previously selected write blocks */
CMD55_APP_CMD = 55, /* Extend to Applications specific commands */
CMD56_GEN_CMD = 56, /* General Purpose Command */
CMD58_READ_OCR = 58, /* Read OCR register of card */
CMD59_CRC_ON_OFF = 59, /* Turns the CRC option on or off*/
// App Commands
ACMD6_SET_BUS_WIDTH = 6,
ACMD13_SD_STATUS = 13,
ACMD22_SEND_NUM_WR_BLOCKS = 22,
ACMD23_SET_WR_BLK_ERASE_COUNT = 23,
ACMD41_SD_SEND_OP_COND = 41,
ACMD42_SET_CLR_CARD_DETECT = 42,
ACMD51_SEND_SCR = 51,
} cmdSupported;
//------------------------------------------------------------------------------
///* Disk Status Bits (DSTATUS) */
// See diskio.h.
// enum {
// STA_NOINIT = 0x01, /* Drive not initialized */
// STA_NODISK = 0x02, /* No medium in the drive */
// STA_PROTECT = 0x04 /* Write protected */
//};
#ifdef __cplusplus
}
#endif
#pragma once
#include <stdint.h>
#include "util.h"
#ifdef __cplusplus
extern "C" {
#endif
/*
+-----------------------+-------+-------+-----------+
| Name | Field | Width | CID-slice |
+-----------------------+-------+-------+-----------+
| Manufacturer ID | MID | 8 | [127:120] |
| OEM/Application ID | OID | 16 | [119:104] |
| Product name | PNM | 40 | [103:64] |
| Product revision | PRV | 8 | [63:56] |
| Product serial number | PSN | 32 | [55:24] |
| reserved | -- | 4 | [23:20] |
| Manufacturing date | MDT | 12 | [19:8] |
| CRC7 checksum | CRC | 7 | [7:1] |
| not used, always 1- | 1 | [0:0] | |
+-----------------------+-------+-------+-----------+
*/
// Table 5-2: The CID Fields
typedef uint8_t CID_t[16];
/*
+---------------+-----------------------+-------------------------------------+
| CSD_STRUCTURE | CSD structure version | Card Capacity |
+---------------+-----------------------+-------------------------------------+
| 0 | CSD Version 1.0 | Standard Capacity |
| 1 | CSD Version 2.0 | High Capacity and Extended Capacity |
| 2-3 | reserved | |
+---------------+-----------------------+-------------------------------------+
*/
// Table 5-3: CSD Register Structure
/*
+--------------------------------------------------+--------------------+-------+---------------+-----------+-----------+
| Name | Field | Width | Value | Cell Type | CSD-slice |
+--------------------------------------------------+--------------------+-------+---------------+-----------+-----------+
| CSD structure | CSD_STRUCTURE | 2 | 00b | R | [127:126] |
| reserved | - | 6 | 00 0000b | R | [125:120] |
| data read access-time-1 | TAAC | 8 | xxh | R | [119:112] |
| data read access-time-2 in CLK cycles (NSAC*100) | NSAC | 8 | xxh | R | [111:104] |
| max. data transfer rate | TRAN_SPEED | 8 | 32h or 5Ah | R | [103:96] |
| card command classes | CCC | 12 | 01x110110101b | R | [95:84] |
| max. read data block length | READ_BL_LEN | 4 | xh | R | [83:80] |
| partial blocks for read allowed | READ_BL_PARTIAL | 1 | 1b | R | [79:79] |
| write block misalignment | WRITE_BLK_MISALIGN | 1 | xb | R | [78:78] |
| read block misalignment | READ_BLK_MISALIGN | 1 | xb | R | [77:77] |
| DSR implemented | DSR_IMP | 1 | xb | R | [76:76] |
| reserved | - | 2 | 00b | R | [75:74] |
| device size | C_SIZE | 12 | xxxh | R | [73:62] |
| max. read current @VDD min | VDD_R_CURR_MIN | 3 | xxxb | R | [61:59] |
| max. read current @VDD max | VDD_R_CURR_MAX | 3 | xxxb | R | [58:56] |
| max. write current @VDD min | VDD_W_CURR_MIN | 3 | xxxb | R | [55:53] |
| max. write current @VDD max | VDD_W_CURR_MAX | 3 | xxxb | R | [52:50] |
| device size multiplier | C_SIZE_MULT | 3 | xxxb | R | [49:47] |
| erase single block enable | ERASE_BLK_EN | 1 | xb | R | [46:46] |
| erase sector size | SECTOR_SIZE | 7 | xxxxxxxb | R | [45:39] |
| write protect group size | WP_GRP_SIZE | 7 | xxxxxxxb | R | [38:32] |
| write protect group enable | WP_GRP_ENABLE | 1 | xb | R | [31:31] |
| reserved | (Do not use) | 2 | 00b | R | [30:29] |
| write speed factor | R2W_FACTOR | 3 | xxxb | R | [28:26] |
| max. write data block length | WRITE_BL_LEN | 4 | xxxxb | R | [25:22] |
| partial blocks for write allowed | WRITE_BL_PARTIAL | 1 | xb | R | [21:21] |
| reserved | - | 5 | 00000b | R | [20:16] |
| File format group | FILE_FORMAT_GRP | 1 | xb | R/W(1) | [15:15] |
| copy flag | COPY | 1 | xb | R/W(1) | [14:14] |
| permanent write protection | PERM_WRITE_PROTECT | 1 | xb | R/W(1) | [13:13] |
| temporary write protection | TMP_WRITE_PROTECT | 1 | xb | R/W | [12:12] |
| File format | FILE_FORMAT | 2 | xxb | R/W(1) | [11:10] |
| reserved | | 2 | 00b | R/W | [9:8] |
| CRC | CRC | 7 | xxxxxxxb | R/W | [7:1] |
| not used, always'1' | - | 1 | 1b | - | [0:0] |
+--------------------------------------------------+--------------------+-------+---------------+-----------+-----------+
*/
// Table 5-4: The CSD Register Fields (CSD Version 1.0)
/*
+------------------------------------------------+----------------------+-------+----------------------+-----------+-----------+
| Name | Field | Width | Value | Cell Type | CSD-slice |
+------------------------------------------------+----------------------+-------+----------------------+-----------+-----------+
| CSD structure | CSD_STRUCTURE | 2 | 01b | R | [127:126] |
| reserved | - | 6 | 00 0000b | R | [125:120] |
| data read access-time | (TAAC) | 8 | 0Eh | R | [119:112] |
| data read access-time in CLK cycles (NSAC*100) | (NSAC) | 8 | 00h | R | [111:104] |
| max. data transfer rate | (TRAN_SPEED) | 8 | 32h, 5Ah, 0Bh or 2Bh | R | [103:96] |
| card command classes | CCC | 12 | 01x110110101b | R | [95:84] |
| max. read data block length | (READ_BL_LEN) | 4 | 9 | R | [83:80] |
| partial blocks for read allowed | (READ_BL_PARTIAL) | 1 | 0 | R | [79:79] |
| write block misalignment | (WRITE_BLK_MISALIGN) | 1 | 0 | R | [78:78] |
| read block misalignment | (READ_BLK_MISALIGN) | 1 | 0 | R | [77:77] |
| DSR implemented | DSR_IMP | 1 | x | R | [76:76] |
| reserved | - | 6 | 00 0000b | R | [75:70] |
| device size | C_SIZE | 22 | xxxxxxh | R | [69:48] |
| reserved | - | 1 | 0 | R | [47:47] |
| erase single block enable | (ERASE_BLK_EN) | 1 | 1 | R | [46:46] |
| erase sector size | (SECTOR_SIZE) | 7 | 7Fh | R | [45:39] |
| write protect group size | (WP_GRP_SIZE) | 7 | 0000000b | R | [38:32] |
| write protect group enable | (WP_GRP_ENABLE) | 1 | 0 | R | [31:31] |
| reserved | | 2 | 00b | R | [30:29] |
| write speed factor | (R2W_FACTOR) | 3 | 010b | R | [28:26] |
| max. write data block length | (WRITE_BL_LEN) | 4 | 9 | R | [25:22] |
| partial blocks for write allowed | (WRITE_BL_PARTIAL) | 1 | 0 | R | [21:21] |
| reserved | - | 5 | 00000b | R | [20:16] |
| File format group | (FILE_FORMAT_GRP) | 1 | 0 | R | [15:15] |
| copy flag | COPY | 1 | x | R/W(1) | [14:14] |
| permanent write protection | PERM_WRITE_PROTECT | 1 | x | R/W(1) | [13:13] |
| temporary write protection | TMP_WRITE_PROTECT | 1 | x | R/W | [12:12] |
| File format | (FILE_FORMAT) | 2 | 00b | R | [11:10] |
| reserved | - | 2 | 00b | R | [9:8] |
| CRC | CRC | 7 | xxxxxxxb | R/W | [7:1] |
| not used, always'1' | - | 1 | 1 | - | [0:0] |
+------------------------------------------------+----------------------+-------+----------------------+-----------+-----------+
*/
// Table 5-16: The CSD Register Fields (CSD Version 2.0)
typedef uint8_t CSD_t[16];
/* return Capacity in sectors */
static inline uint32_t CSD_sectors(CSD_t csd) /* const */ {
uint32_t c_size;
// +--------------------------------------------------+--------------------+-------+---------------+-----------+-----------+
// | Name | Field | Width | Value | Cell Type | CSD-slice |
// +--------------------------------------------------+--------------------+-------+---------------+-----------+-----------+
// | CSD structure | CSD_STRUCTURE | 2 | 00b | R | [127:126] |
uint8_t ver = ext_bits16(csd, 127, 126);
if (ver == 0) {
// | device size | C_SIZE | 12 | xxxh | R | [73:62] |
c_size = ext_bits16(csd, 73, 62);
// | device size multiplier | C_SIZE_MULT | 3 | xxxb | R | [49:47] |
uint8_t c_size_mult = ext_bits16(csd, 49, 47);
// MULT = 2^(C_SIZE_MULT+2)
uint32_t mult = 1UL << (c_size_mult + 2);
// BLOCKNR = (C_SIZE+1) * MULT
return (c_size + 1) * mult;
} else if (ver == 1) {
// | device size | C_SIZE | 22 | xxxxxxh | R | [69:48] |
c_size = ext_bits16(csd, 69, 48);
/* The user data area capacity is calculated from C_SIZE as follows:
memory capacity = (C_SIZE+1) * 512K byte */
return (c_size + 1) * 1024; // sectors
} else {
return 0;
}
}
#ifdef __cplusplus
}
#endif
/* FatFsSd.cpp
Copyright 2023 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#include "FatFsSd.h"
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
using namespace FatFsNs;
/*
See FatFs - Generic FAT Filesystem Module, "Application Interface",
http://elm-chan.org/fsw/ff/00index_e.html
*/
std::vector<SdCard> FatFs::SdCards;
/* Put a formatted string to the file */
int File::printf(const TCHAR* format, ...) {
va_list arg;
va_start(arg, format);
char temp[64];
char* buffer = temp;
size_t len = vsnprintf(temp, sizeof(temp), format, arg);
va_end(arg);
if (len > sizeof(temp) - 1) {
buffer = new char[len + 1];
if (!buffer) {
return 0;
}
va_start(arg, format);
int vrc = vsnprintf(buffer, len + 1, format, arg);
// Notice that only when this returned value is non-negative and less than n,
// the string has been completely written.
assert(vrc >= 0 && vrc < len + 1);
va_end(arg);
}
UINT bw;
FRESULT fr = f_write(&fil, buffer, len, &bw);
int rc = bw;
if (FR_OK != fr) {
rc = -1;
}
if (buffer != temp) {
delete[] buffer;
}
return rc;
}
bool FatFs::begin() {
if (!sd_init_driver())
return false;
for (size_t i = 0; i < sd_get_num(); ++i) {
sd_card_t* sd_card_p = sd_get_by_num(i);
if (!sd_card_p) return false;
// See http://elm-chan.org/fsw/ff/doc/dstat.html
int dstatus = sd_card_p->init(sd_card_p);
if (dstatus & STA_NOINIT) return false;
}
return true;
}
size_t __attribute__((weak)) sd_get_num() {
return FatFs::SdCard_get_num();
}
sd_card_t * __attribute__((weak)) sd_get_by_num(size_t num) {
return (FatFs::SdCard_get_by_num(num)->m_sd_card_p);
}
/* crash.c
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#include <string.h>
#include <time.h>
//
#include "pico/stdlib.h"
//
#include "crc.h"
#include "my_debug.h"
#include "my_rtc.h"
#include "util.h"
//
#include "crash.h"
#if defined(NDEBUG) || !USE_DBG_PRINTF
# pragma GCC diagnostic ignored "-Wunused-variable"
#endif
static volatile crash_info_t crash_info_ram __attribute__((section(".uninitialized_data")));
static crash_info_t volatile *crash_info_ram_p = &crash_info_ram;
static crash_info_t previous_crash_info;
static bool _previous_crash_info_valid = false;
__attribute__((noreturn, always_inline))
static inline void reset() {
// if (debugger_connected()) {
__breakpoint();
// } else {
NVIC_SystemReset();
// }
__builtin_unreachable();
}
void crash_handler_init() {
if (crash_info_ram.magic == crash_magic_hard_fault ||
crash_info_ram.magic == crash_magic_stack_overflow ||
crash_info_ram.magic == crash_magic_reboot_requested ||
crash_info_ram.magic == crash_magic_assert ||
crash_info_ram.magic == crash_magic_debug_mon) {
uint8_t xor_checksum = crc7((uint8_t *)crash_info_ram_p,
offsetof(crash_info_t, xor_checksum));
if (xor_checksum == crash_info_ram.xor_checksum) {
// valid crash record
memcpy(&previous_crash_info, (void *)crash_info_ram_p, sizeof previous_crash_info);
_previous_crash_info_valid = true;
}
}
memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram);
}
const crash_info_t *crash_handler_get_info() {
if (_previous_crash_info_valid) {
return &previous_crash_info;
}
return NULL;
}
__attribute__((noreturn))
void system_reset_func(char const *const func) {
memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram);
crash_info_ram.magic = crash_magic_reboot_requested;
crash_info_ram.timestamp = epochtime;
snprintf((char *)crash_info_ram.calling_func, sizeof crash_info_ram.calling_func, "%s",
func);
crash_info_ram.xor_checksum =
crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum));
__DSB();
reset();
__builtin_unreachable();
}
__attribute__((noreturn))
void capture_assert(const char *file, int line, const char *func, const char *pred) {
memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram);
crash_info_ram.magic = crash_magic_assert;
crash_info_ram.timestamp = epochtime;
// If the filename is too long, take the end:
size_t full_len = strlen(file) + 1;
size_t offset = 0;
if (full_len > sizeof crash_info_ram.assert.file) {
offset = full_len - sizeof crash_info_ram.assert.file;
}
snprintf((char *)crash_info_ram.assert.file, sizeof crash_info_ram.assert.file, "%s",
file + offset);
snprintf((char *)crash_info_ram.assert.func, sizeof crash_info_ram.assert.func, "%s", func);
snprintf((char *)crash_info_ram.assert.pred, sizeof crash_info_ram.assert.pred, "%s", pred);
crash_info_ram.assert.line = line;
crash_info_ram.xor_checksum =
crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum));
__DSB();
reset();
__builtin_unreachable();
}
__attribute__((used)) extern void DebugMon_HandlerC(uint32_t const *faultStackAddr) {
memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram);
crash_info_ram.magic = crash_magic_debug_mon;
crash_info_ram.timestamp = epochtime;
/* Stores general registers */
crash_info_ram.cy_faultFrame.r0 = faultStackAddr[R0_Pos];
crash_info_ram.cy_faultFrame.r1 = faultStackAddr[R1_Pos];
crash_info_ram.cy_faultFrame.r2 = faultStackAddr[R2_Pos];
crash_info_ram.cy_faultFrame.r3 = faultStackAddr[R3_Pos];
crash_info_ram.cy_faultFrame.r12 = faultStackAddr[R12_Pos];
crash_info_ram.cy_faultFrame.lr = faultStackAddr[LR_Pos];
crash_info_ram.cy_faultFrame.pc = faultStackAddr[PC_Pos];
crash_info_ram.cy_faultFrame.psr = faultStackAddr[PSR_Pos];
///* Stores the Configurable Fault Status Register state with the fault cause */
//crash_info_ram.cy_faultFrame.cfsr.cfsrReg = SCB->CFSR;
///* Stores the Hard Fault Status Register */
//crash_info_ram.cy_faultFrame.hfsr.hfsrReg = SCB->HFSR;
///* Stores the System Handler Control and State Register */
//crash_info_ram.cy_faultFrame.shcsr.shcsrReg = SCB->SHCSR;
///* Store MemMange fault address */
//crash_info_ram.cy_faultFrame.mmfar = SCB->MMFAR;
///* Store Bus fault address */
//crash_info_ram.cy_faultFrame.bfar = SCB->BFAR;
volatile uint8_t __attribute__((unused)) watchpoint_number = 0;
// if (DWT->FUNCTION0 & DWT_FUNCTION_MATCHED_Msk) {
// watchpoint_number = 0;
//} else if (DWT->FUNCTION1 & DWT_FUNCTION_MATCHED_Msk) {
// watchpoint_number = 1;
//} else if (DWT->FUNCTION2 & DWT_FUNCTION_MATCHED_Msk) {
// watchpoint_number = 2;
//}
crash_info_ram.xor_checksum =
crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum));
__DSB(); // make sure all data is really written into the memory before
// doing a reset
reset();
}
extern void DebugMon_Handler(void);
__attribute__((naked)) void DebugMon_Handler(void) {
__asm volatile(
" movs r0,#4 \n"
" movs r1, lr \n"
" tst r0, r1 \n"
" beq _MSP2 \n"
" mrs r0, psp \n"
" b _HALT2 \n"
"_MSP2: \n"
" mrs r0, msp \n"
"_HALT2: \n"
" ldr r1,[r0,#20] \n"
" b DebugMon_HandlerC \n");
}
void Hardfault_HandlerC(uint32_t const *faultStackAddr) {
memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram);
crash_info_ram.magic = crash_magic_hard_fault;
crash_info_ram.timestamp = epochtime;
/* Stores general registers */
crash_info_ram.cy_faultFrame.r0 = faultStackAddr[R0_Pos];
crash_info_ram.cy_faultFrame.r1 = faultStackAddr[R1_Pos];
crash_info_ram.cy_faultFrame.r2 = faultStackAddr[R2_Pos];
crash_info_ram.cy_faultFrame.r3 = faultStackAddr[R3_Pos];
crash_info_ram.cy_faultFrame.r12 = faultStackAddr[R12_Pos];
crash_info_ram.cy_faultFrame.lr = faultStackAddr[LR_Pos];
crash_info_ram.cy_faultFrame.pc = faultStackAddr[PC_Pos];
crash_info_ram.cy_faultFrame.psr = faultStackAddr[PSR_Pos];
crash_info_ram.xor_checksum =
crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum));
__DSB(); // make sure all data is really written into the memory before
// doing a reset
reset();
}
__attribute__((naked)) void isr_hardfault(void) {
__asm volatile(
" movs r0,#4 \n"
" movs r1, lr \n"
" tst r0, r1 \n"
" beq _MSP3 \n"
" mrs r0, psp \n"
" b _HALT3 \n"
"_MSP3: \n"
" mrs r0, msp \n"
"_HALT3: \n"
" ldr r1,[r0,#20] \n"
" b Hardfault_HandlerC \n");
}
enum {
crash_info_magic,
crash_info_hf_lr,
crash_info_hf_pc,
crash_info_reset_request_line,
crash_info_assert
};
int dump_crash_info(crash_info_t const *const crash_info_p, int next, char *const buf,
size_t const buf_sz) {
int nwrit = 0;
switch (next) {
case crash_info_magic:
nwrit = snprintf(buf, buf_sz, "Event: ");
switch (crash_info_p->magic) {
case crash_magic_none:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tNone.");
next = 0;
break;
case crash_magic_bootloader_entry:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tBootloader Entry.");
next = 0;
break;
case crash_magic_hard_fault:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tCM4 Hard Fault.");
next = crash_info_hf_lr;
break;
case crash_magic_debug_mon:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit,
"\tDebug Monitor Watchpoint Triggered.");
next = crash_info_hf_lr;
break;
case crash_magic_reboot_requested:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tReboot Requested.");
next = crash_info_reset_request_line;
break;
case crash_magic_assert:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tAssertion Failed.");
next = crash_info_assert;
break;
default:
DBG_ASSERT_CASE_NOT(crash_info_p->magic);
next = 0;
}
{
struct tm tmbuf;
struct tm *ptm = localtime_r(&crash_info_p->timestamp, &tmbuf);
char tsbuf[32];
size_t n = strftime(tsbuf, sizeof tsbuf, "\n\tTime: %Y-%m-%d %H:%M:%S\n", ptm);
myASSERT(n);
nwrit = snprintf(buf + nwrit, buf_sz - nwrit, "%s", tsbuf);
}
break;
case crash_info_hf_lr:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tLink Register (LR): %p\n",
(void *)crash_info_p->cy_faultFrame.lr);
next = crash_info_hf_pc;
break;
case crash_info_hf_pc:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tProgram Counter (PC): %p\n",
(void *)crash_info_p->cy_faultFrame.pc);
next = 0;
break;
case crash_info_reset_request_line:
nwrit +=
snprintf(buf + nwrit, buf_sz - nwrit, "\tReset request calling function: %s\n",
crash_info_p->calling_func);
next = 0;
break;
case crash_info_assert:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit,
"\tAssertion \"%s\" failed: file \"%s\", line "
"%d, function: %s\n",
crash_info_p->assert.pred, crash_info_p->assert.file,
crash_info_p->assert.line, crash_info_p->assert.func);
next = 0;
break;
default:
ASSERT_CASE_NOT(crash_info_p->magic);
}
return next;
}
/* [] END OF FILE */
/* crc.c
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
/* Partially derived from:
* crcany (https://github.com/madler/crcany)
* License
* This code is under the zlib license, permitting free commercial use.
*/
#include "crc.h"
const char m_Crc7Table[] = {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};
/* The CRC check sum is a 16-bit value and is computed as follows:
Generator polynomial G(x) = x^16 +x^12 +x^5 +1
M(x) = (first bit) * x^n + (second bit)* x^(n-1) +...+ (last bit) * x^0
CRC[15...0] = Remainder [(M(x) * x^16)/G(x)]
The first bit is the first data bit of the corresponding block. The degree n of the polynomial denotes the
number of bits of the data block decreased by one (e.g. n = 4095 for a block length of 512 bytes). The
generator polynomial G(x) is a standard CCITT polynomial. The code has a minimal distance d=4 and is
used for a payload length of up to 2048 Bytes (n <= 16383).
A CRC is called an n-bit CRC when its check value is n-bits.
For a given n, multiple CRCs are possible, each with a different polynomial.
Such a polynomial has highest degree n, and hence n + 1 terms (the polynomial has a length of n + 1).
The remainder has length n.
The CRC has a name of the form CRC-n-XXX.
Omission of the high-order bit of the divisor polynomial:
Since the high-order bit is always 1, and since an n-bit CRC must be defined by
an (n + 1)-bit divisor which overflows an n-bit register,
some writers assume that it is unnecessary to mention the divisor's high-order bit.
Omission of the low-order bit of the divisor polynomial:
Since the low-order bit is always 1, authors such as Philip Koopman represent polynomials
with their high-order bit intact, but without the low-order bit (the x^0 or 1 term).
This convention encodes the polynomial complete with its degree in one integer.
https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-16-ibm-3740
https://github.com/madler/crcany
16-bit CRC-CCITT specification, is: (CRC-16/IBM-3740? An algorithm commonly misidentified as CRC-CCITT.)
Width = 16 bits
Truncated polynomial = 0x1021
Initial value = 0xFFFF (This doesn't seem to be how it works for SD cards!)
Input data is NOT reflected
Output CRC is NOT reflected
No XOR is performed on the output CRC
Polynomial representations
Normal Reversed Reciprocal Reversed reciprocal
0x1021 0x8408 0x811 0x8810
1
2109876543210
0x1021 = 0b1000000100001 = 4129
*/
static uint16_t const table_byte[] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129,
0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252,
0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c,
0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672,
0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738,
0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861,
0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc,
0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5,
0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b,
0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9,
0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3,
0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c,
0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3,
0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8,
0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676,
0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c,
0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16,
0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b,
0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36,
0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};
static uint16_t const table_word[][256] = {
{0x0000, 0x2110, 0x4220, 0x6330, 0x8440, 0xa550, 0xc660, 0xe770, 0x0881, 0x2991,
0x4aa1, 0x6bb1, 0x8cc1, 0xadd1, 0xcee1, 0xeff1, 0x3112, 0x1002, 0x7332, 0x5222,
0xb552, 0x9442, 0xf772, 0xd662, 0x3993, 0x1883, 0x7bb3, 0x5aa3, 0xbdd3, 0x9cc3,
0xfff3, 0xdee3, 0x6224, 0x4334, 0x2004, 0x0114, 0xe664, 0xc774, 0xa444, 0x8554,
0x6aa5, 0x4bb5, 0x2885, 0x0995, 0xeee5, 0xcff5, 0xacc5, 0x8dd5, 0x5336, 0x7226,
0x1116, 0x3006, 0xd776, 0xf666, 0x9556, 0xb446, 0x5bb7, 0x7aa7, 0x1997, 0x3887,
0xdff7, 0xfee7, 0x9dd7, 0xbcc7, 0xc448, 0xe558, 0x8668, 0xa778, 0x4008, 0x6118,
0x0228, 0x2338, 0xccc9, 0xedd9, 0x8ee9, 0xaff9, 0x4889, 0x6999, 0x0aa9, 0x2bb9,
0xf55a, 0xd44a, 0xb77a, 0x966a, 0x711a, 0x500a, 0x333a, 0x122a, 0xfddb, 0xdccb,
0xbffb, 0x9eeb, 0x799b, 0x588b, 0x3bbb, 0x1aab, 0xa66c, 0x877c, 0xe44c, 0xc55c,
0x222c, 0x033c, 0x600c, 0x411c, 0xaeed, 0x8ffd, 0xeccd, 0xcddd, 0x2aad, 0x0bbd,
0x688d, 0x499d, 0x977e, 0xb66e, 0xd55e, 0xf44e, 0x133e, 0x322e, 0x511e, 0x700e,
0x9fff, 0xbeef, 0xdddf, 0xfccf, 0x1bbf, 0x3aaf, 0x599f, 0x788f, 0x8891, 0xa981,
0xcab1, 0xeba1, 0x0cd1, 0x2dc1, 0x4ef1, 0x6fe1, 0x8010, 0xa100, 0xc230, 0xe320,
0x0450, 0x2540, 0x4670, 0x6760, 0xb983, 0x9893, 0xfba3, 0xdab3, 0x3dc3, 0x1cd3,
0x7fe3, 0x5ef3, 0xb102, 0x9012, 0xf322, 0xd232, 0x3542, 0x1452, 0x7762, 0x5672,
0xeab5, 0xcba5, 0xa895, 0x8985, 0x6ef5, 0x4fe5, 0x2cd5, 0x0dc5, 0xe234, 0xc324,
0xa014, 0x8104, 0x6674, 0x4764, 0x2454, 0x0544, 0xdba7, 0xfab7, 0x9987, 0xb897,
0x5fe7, 0x7ef7, 0x1dc7, 0x3cd7, 0xd326, 0xf236, 0x9106, 0xb016, 0x5766, 0x7676,
0x1546, 0x3456, 0x4cd9, 0x6dc9, 0x0ef9, 0x2fe9, 0xc899, 0xe989, 0x8ab9, 0xaba9,
0x4458, 0x6548, 0x0678, 0x2768, 0xc018, 0xe108, 0x8238, 0xa328, 0x7dcb, 0x5cdb,
0x3feb, 0x1efb, 0xf98b, 0xd89b, 0xbbab, 0x9abb, 0x754a, 0x545a, 0x376a, 0x167a,
0xf10a, 0xd01a, 0xb32a, 0x923a, 0x2efd, 0x0fed, 0x6cdd, 0x4dcd, 0xaabd, 0x8bad,
0xe89d, 0xc98d, 0x267c, 0x076c, 0x645c, 0x454c, 0xa23c, 0x832c, 0xe01c, 0xc10c,
0x1fef, 0x3eff, 0x5dcf, 0x7cdf, 0x9baf, 0xbabf, 0xd98f, 0xf89f, 0x176e, 0x367e,
0x554e, 0x745e, 0x932e, 0xb23e, 0xd10e, 0xf01e},
{0x0000, 0x3133, 0x6266, 0x5355, 0xc4cc, 0xf5ff, 0xa6aa, 0x9799, 0xa989, 0x98ba,
0xcbef, 0xfadc, 0x6d45, 0x5c76, 0x0f23, 0x3e10, 0x7303, 0x4230, 0x1165, 0x2056,
0xb7cf, 0x86fc, 0xd5a9, 0xe49a, 0xda8a, 0xebb9, 0xb8ec, 0x89df, 0x1e46, 0x2f75,
0x7c20, 0x4d13, 0xe606, 0xd735, 0x8460, 0xb553, 0x22ca, 0x13f9, 0x40ac, 0x719f,
0x4f8f, 0x7ebc, 0x2de9, 0x1cda, 0x8b43, 0xba70, 0xe925, 0xd816, 0x9505, 0xa436,
0xf763, 0xc650, 0x51c9, 0x60fa, 0x33af, 0x029c, 0x3c8c, 0x0dbf, 0x5eea, 0x6fd9,
0xf840, 0xc973, 0x9a26, 0xab15, 0xcc0d, 0xfd3e, 0xae6b, 0x9f58, 0x08c1, 0x39f2,
0x6aa7, 0x5b94, 0x6584, 0x54b7, 0x07e2, 0x36d1, 0xa148, 0x907b, 0xc32e, 0xf21d,
0xbf0e, 0x8e3d, 0xdd68, 0xec5b, 0x7bc2, 0x4af1, 0x19a4, 0x2897, 0x1687, 0x27b4,
0x74e1, 0x45d2, 0xd24b, 0xe378, 0xb02d, 0x811e, 0x2a0b, 0x1b38, 0x486d, 0x795e,
0xeec7, 0xdff4, 0x8ca1, 0xbd92, 0x8382, 0xb2b1, 0xe1e4, 0xd0d7, 0x474e, 0x767d,
0x2528, 0x141b, 0x5908, 0x683b, 0x3b6e, 0x0a5d, 0x9dc4, 0xacf7, 0xffa2, 0xce91,
0xf081, 0xc1b2, 0x92e7, 0xa3d4, 0x344d, 0x057e, 0x562b, 0x6718, 0x981b, 0xa928,
0xfa7d, 0xcb4e, 0x5cd7, 0x6de4, 0x3eb1, 0x0f82, 0x3192, 0x00a1, 0x53f4, 0x62c7,
0xf55e, 0xc46d, 0x9738, 0xa60b, 0xeb18, 0xda2b, 0x897e, 0xb84d, 0x2fd4, 0x1ee7,
0x4db2, 0x7c81, 0x4291, 0x73a2, 0x20f7, 0x11c4, 0x865d, 0xb76e, 0xe43b, 0xd508,
0x7e1d, 0x4f2e, 0x1c7b, 0x2d48, 0xbad1, 0x8be2, 0xd8b7, 0xe984, 0xd794, 0xe6a7,
0xb5f2, 0x84c1, 0x1358, 0x226b, 0x713e, 0x400d, 0x0d1e, 0x3c2d, 0x6f78, 0x5e4b,
0xc9d2, 0xf8e1, 0xabb4, 0x9a87, 0xa497, 0x95a4, 0xc6f1, 0xf7c2, 0x605b, 0x5168,
0x023d, 0x330e, 0x5416, 0x6525, 0x3670, 0x0743, 0x90da, 0xa1e9, 0xf2bc, 0xc38f,
0xfd9f, 0xccac, 0x9ff9, 0xaeca, 0x3953, 0x0860, 0x5b35, 0x6a06, 0x2715, 0x1626,
0x4573, 0x7440, 0xe3d9, 0xd2ea, 0x81bf, 0xb08c, 0x8e9c, 0xbfaf, 0xecfa, 0xddc9,
0x4a50, 0x7b63, 0x2836, 0x1905, 0xb210, 0x8323, 0xd076, 0xe145, 0x76dc, 0x47ef,
0x14ba, 0x2589, 0x1b99, 0x2aaa, 0x79ff, 0x48cc, 0xdf55, 0xee66, 0xbd33, 0x8c00,
0xc113, 0xf020, 0xa375, 0x9246, 0x05df, 0x34ec, 0x67b9, 0x568a, 0x689a, 0x59a9,
0x0afc, 0x3bcf, 0xac56, 0x9d65, 0xce30, 0xff03},
{0x0000, 0x3037, 0x606e, 0x5059, 0xc0dc, 0xf0eb, 0xa0b2, 0x9085, 0xa1a9, 0x919e,
0xc1c7, 0xf1f0, 0x6175, 0x5142, 0x011b, 0x312c, 0x6343, 0x5374, 0x032d, 0x331a,
0xa39f, 0x93a8, 0xc3f1, 0xf3c6, 0xc2ea, 0xf2dd, 0xa284, 0x92b3, 0x0236, 0x3201,
0x6258, 0x526f, 0xc686, 0xf6b1, 0xa6e8, 0x96df, 0x065a, 0x366d, 0x6634, 0x5603,
0x672f, 0x5718, 0x0741, 0x3776, 0xa7f3, 0x97c4, 0xc79d, 0xf7aa, 0xa5c5, 0x95f2,
0xc5ab, 0xf59c, 0x6519, 0x552e, 0x0577, 0x3540, 0x046c, 0x345b, 0x6402, 0x5435,
0xc4b0, 0xf487, 0xa4de, 0x94e9, 0xad1d, 0x9d2a, 0xcd73, 0xfd44, 0x6dc1, 0x5df6,
0x0daf, 0x3d98, 0x0cb4, 0x3c83, 0x6cda, 0x5ced, 0xcc68, 0xfc5f, 0xac06, 0x9c31,
0xce5e, 0xfe69, 0xae30, 0x9e07, 0x0e82, 0x3eb5, 0x6eec, 0x5edb, 0x6ff7, 0x5fc0,
0x0f99, 0x3fae, 0xaf2b, 0x9f1c, 0xcf45, 0xff72, 0x6b9b, 0x5bac, 0x0bf5, 0x3bc2,
0xab47, 0x9b70, 0xcb29, 0xfb1e, 0xca32, 0xfa05, 0xaa5c, 0x9a6b, 0x0aee, 0x3ad9,
0x6a80, 0x5ab7, 0x08d8, 0x38ef, 0x68b6, 0x5881, 0xc804, 0xf833, 0xa86a, 0x985d,
0xa971, 0x9946, 0xc91f, 0xf928, 0x69ad, 0x599a, 0x09c3, 0x39f4, 0x5a3b, 0x6a0c,
0x3a55, 0x0a62, 0x9ae7, 0xaad0, 0xfa89, 0xcabe, 0xfb92, 0xcba5, 0x9bfc, 0xabcb,
0x3b4e, 0x0b79, 0x5b20, 0x6b17, 0x3978, 0x094f, 0x5916, 0x6921, 0xf9a4, 0xc993,
0x99ca, 0xa9fd, 0x98d1, 0xa8e6, 0xf8bf, 0xc888, 0x580d, 0x683a, 0x3863, 0x0854,
0x9cbd, 0xac8a, 0xfcd3, 0xcce4, 0x5c61, 0x6c56, 0x3c0f, 0x0c38, 0x3d14, 0x0d23,
0x5d7a, 0x6d4d, 0xfdc8, 0xcdff, 0x9da6, 0xad91, 0xfffe, 0xcfc9, 0x9f90, 0xafa7,
0x3f22, 0x0f15, 0x5f4c, 0x6f7b, 0x5e57, 0x6e60, 0x3e39, 0x0e0e, 0x9e8b, 0xaebc,
0xfee5, 0xced2, 0xf726, 0xc711, 0x9748, 0xa77f, 0x37fa, 0x07cd, 0x5794, 0x67a3,
0x568f, 0x66b8, 0x36e1, 0x06d6, 0x9653, 0xa664, 0xf63d, 0xc60a, 0x9465, 0xa452,
0xf40b, 0xc43c, 0x54b9, 0x648e, 0x34d7, 0x04e0, 0x35cc, 0x05fb, 0x55a2, 0x6595,
0xf510, 0xc527, 0x957e, 0xa549, 0x31a0, 0x0197, 0x51ce, 0x61f9, 0xf17c, 0xc14b,
0x9112, 0xa125, 0x9009, 0xa03e, 0xf067, 0xc050, 0x50d5, 0x60e2, 0x30bb, 0x008c,
0x52e3, 0x62d4, 0x328d, 0x02ba, 0x923f, 0xa208, 0xf251, 0xc266, 0xf34a, 0xc37d,
0x9324, 0xa313, 0x3396, 0x03a1, 0x53f8, 0x63cf},
{0x0000, 0xb476, 0x68ed, 0xdc9b, 0xf1ca, 0x45bc, 0x9927, 0x2d51, 0xc385, 0x77f3,
0xab68, 0x1f1e, 0x324f, 0x8639, 0x5aa2, 0xeed4, 0xa71b, 0x136d, 0xcff6, 0x7b80,
0x56d1, 0xe2a7, 0x3e3c, 0x8a4a, 0x649e, 0xd0e8, 0x0c73, 0xb805, 0x9554, 0x2122,
0xfdb9, 0x49cf, 0x4e37, 0xfa41, 0x26da, 0x92ac, 0xbffd, 0x0b8b, 0xd710, 0x6366,
0x8db2, 0x39c4, 0xe55f, 0x5129, 0x7c78, 0xc80e, 0x1495, 0xa0e3, 0xe92c, 0x5d5a,
0x81c1, 0x35b7, 0x18e6, 0xac90, 0x700b, 0xc47d, 0x2aa9, 0x9edf, 0x4244, 0xf632,
0xdb63, 0x6f15, 0xb38e, 0x07f8, 0x9c6e, 0x2818, 0xf483, 0x40f5, 0x6da4, 0xd9d2,
0x0549, 0xb13f, 0x5feb, 0xeb9d, 0x3706, 0x8370, 0xae21, 0x1a57, 0xc6cc, 0x72ba,
0x3b75, 0x8f03, 0x5398, 0xe7ee, 0xcabf, 0x7ec9, 0xa252, 0x1624, 0xf8f0, 0x4c86,
0x901d, 0x246b, 0x093a, 0xbd4c, 0x61d7, 0xd5a1, 0xd259, 0x662f, 0xbab4, 0x0ec2,
0x2393, 0x97e5, 0x4b7e, 0xff08, 0x11dc, 0xa5aa, 0x7931, 0xcd47, 0xe016, 0x5460,
0x88fb, 0x3c8d, 0x7542, 0xc134, 0x1daf, 0xa9d9, 0x8488, 0x30fe, 0xec65, 0x5813,
0xb6c7, 0x02b1, 0xde2a, 0x6a5c, 0x470d, 0xf37b, 0x2fe0, 0x9b96, 0x38dd, 0x8cab,
0x5030, 0xe446, 0xc917, 0x7d61, 0xa1fa, 0x158c, 0xfb58, 0x4f2e, 0x93b5, 0x27c3,
0x0a92, 0xbee4, 0x627f, 0xd609, 0x9fc6, 0x2bb0, 0xf72b, 0x435d, 0x6e0c, 0xda7a,
0x06e1, 0xb297, 0x5c43, 0xe835, 0x34ae, 0x80d8, 0xad89, 0x19ff, 0xc564, 0x7112,
0x76ea, 0xc29c, 0x1e07, 0xaa71, 0x8720, 0x3356, 0xefcd, 0x5bbb, 0xb56f, 0x0119,
0xdd82, 0x69f4, 0x44a5, 0xf0d3, 0x2c48, 0x983e, 0xd1f1, 0x6587, 0xb91c, 0x0d6a,
0x203b, 0x944d, 0x48d6, 0xfca0, 0x1274, 0xa602, 0x7a99, 0xceef, 0xe3be, 0x57c8,
0x8b53, 0x3f25, 0xa4b3, 0x10c5, 0xcc5e, 0x7828, 0x5579, 0xe10f, 0x3d94, 0x89e2,
0x6736, 0xd340, 0x0fdb, 0xbbad, 0x96fc, 0x228a, 0xfe11, 0x4a67, 0x03a8, 0xb7de,
0x6b45, 0xdf33, 0xf262, 0x4614, 0x9a8f, 0x2ef9, 0xc02d, 0x745b, 0xa8c0, 0x1cb6,
0x31e7, 0x8591, 0x590a, 0xed7c, 0xea84, 0x5ef2, 0x8269, 0x361f, 0x1b4e, 0xaf38,
0x73a3, 0xc7d5, 0x2901, 0x9d77, 0x41ec, 0xf59a, 0xd8cb, 0x6cbd, 0xb026, 0x0450,
0x4d9f, 0xf9e9, 0x2572, 0x9104, 0xbc55, 0x0823, 0xd4b8, 0x60ce, 0x8e1a, 0x3a6c,
0xe6f7, 0x5281, 0x7fd0, 0xcba6, 0x173d, 0xa34b},
{0x0000, 0x51aa, 0x8344, 0xd2ee, 0x0689, 0x5723, 0x85cd, 0xd467, 0x2d02, 0x7ca8,
0xae46, 0xffec, 0x2b8b, 0x7a21, 0xa8cf, 0xf965, 0x5a04, 0x0bae, 0xd940, 0x88ea,
0x5c8d, 0x0d27, 0xdfc9, 0x8e63, 0x7706, 0x26ac, 0xf442, 0xa5e8, 0x718f, 0x2025,
0xf2cb, 0xa361, 0xb408, 0xe5a2, 0x374c, 0x66e6, 0xb281, 0xe32b, 0x31c5, 0x606f,
0x990a, 0xc8a0, 0x1a4e, 0x4be4, 0x9f83, 0xce29, 0x1cc7, 0x4d6d, 0xee0c, 0xbfa6,
0x6d48, 0x3ce2, 0xe885, 0xb92f, 0x6bc1, 0x3a6b, 0xc30e, 0x92a4, 0x404a, 0x11e0,
0xc587, 0x942d, 0x46c3, 0x1769, 0x6811, 0x39bb, 0xeb55, 0xbaff, 0x6e98, 0x3f32,
0xeddc, 0xbc76, 0x4513, 0x14b9, 0xc657, 0x97fd, 0x439a, 0x1230, 0xc0de, 0x9174,
0x3215, 0x63bf, 0xb151, 0xe0fb, 0x349c, 0x6536, 0xb7d8, 0xe672, 0x1f17, 0x4ebd,
0x9c53, 0xcdf9, 0x199e, 0x4834, 0x9ada, 0xcb70, 0xdc19, 0x8db3, 0x5f5d, 0x0ef7,
0xda90, 0x8b3a, 0x59d4, 0x087e, 0xf11b, 0xa0b1, 0x725f, 0x23f5, 0xf792, 0xa638,
0x74d6, 0x257c, 0x861d, 0xd7b7, 0x0559, 0x54f3, 0x8094, 0xd13e, 0x03d0, 0x527a,
0xab1f, 0xfab5, 0x285b, 0x79f1, 0xad96, 0xfc3c, 0x2ed2, 0x7f78, 0xd022, 0x8188,
0x5366, 0x02cc, 0xd6ab, 0x8701, 0x55ef, 0x0445, 0xfd20, 0xac8a, 0x7e64, 0x2fce,
0xfba9, 0xaa03, 0x78ed, 0x2947, 0x8a26, 0xdb8c, 0x0962, 0x58c8, 0x8caf, 0xdd05,
0x0feb, 0x5e41, 0xa724, 0xf68e, 0x2460, 0x75ca, 0xa1ad, 0xf007, 0x22e9, 0x7343,
0x642a, 0x3580, 0xe76e, 0xb6c4, 0x62a3, 0x3309, 0xe1e7, 0xb04d, 0x4928, 0x1882,
0xca6c, 0x9bc6, 0x4fa1, 0x1e0b, 0xcce5, 0x9d4f, 0x3e2e, 0x6f84, 0xbd6a, 0xecc0,
0x38a7, 0x690d, 0xbbe3, 0xea49, 0x132c, 0x4286, 0x9068, 0xc1c2, 0x15a5, 0x440f,
0x96e1, 0xc74b, 0xb833, 0xe999, 0x3b77, 0x6add, 0xbeba, 0xef10, 0x3dfe, 0x6c54,
0x9531, 0xc49b, 0x1675, 0x47df, 0x93b8, 0xc212, 0x10fc, 0x4156, 0xe237, 0xb39d,
0x6173, 0x30d9, 0xe4be, 0xb514, 0x67fa, 0x3650, 0xcf35, 0x9e9f, 0x4c71, 0x1ddb,
0xc9bc, 0x9816, 0x4af8, 0x1b52, 0x0c3b, 0x5d91, 0x8f7f, 0xded5, 0x0ab2, 0x5b18,
0x89f6, 0xd85c, 0x2139, 0x7093, 0xa27d, 0xf3d7, 0x27b0, 0x761a, 0xa4f4, 0xf55e,
0x563f, 0x0795, 0xd57b, 0x84d1, 0x50b6, 0x011c, 0xd3f2, 0x8258, 0x7b3d, 0x2a97,
0xf879, 0xa9d3, 0x7db4, 0x2c1e, 0xfef0, 0xaf5a},
{0x0000, 0xa045, 0x408b, 0xe0ce, 0xa106, 0x0143, 0xe18d, 0x41c8, 0x420d, 0xe248,
0x0286, 0xa2c3, 0xe30b, 0x434e, 0xa380, 0x03c5, 0x841a, 0x245f, 0xc491, 0x64d4,
0x251c, 0x8559, 0x6597, 0xc5d2, 0xc617, 0x6652, 0x869c, 0x26d9, 0x6711, 0xc754,
0x279a, 0x87df, 0x0835, 0xa870, 0x48be, 0xe8fb, 0xa933, 0x0976, 0xe9b8, 0x49fd,
0x4a38, 0xea7d, 0x0ab3, 0xaaf6, 0xeb3e, 0x4b7b, 0xabb5, 0x0bf0, 0x8c2f, 0x2c6a,
0xcca4, 0x6ce1, 0x2d29, 0x8d6c, 0x6da2, 0xcde7, 0xce22, 0x6e67, 0x8ea9, 0x2eec,
0x6f24, 0xcf61, 0x2faf, 0x8fea, 0x106a, 0xb02f, 0x50e1, 0xf0a4, 0xb16c, 0x1129,
0xf1e7, 0x51a2, 0x5267, 0xf222, 0x12ec, 0xb2a9, 0xf361, 0x5324, 0xb3ea, 0x13af,
0x9470, 0x3435, 0xd4fb, 0x74be, 0x3576, 0x9533, 0x75fd, 0xd5b8, 0xd67d, 0x7638,
0x96f6, 0x36b3, 0x777b, 0xd73e, 0x37f0, 0x97b5, 0x185f, 0xb81a, 0x58d4, 0xf891,
0xb959, 0x191c, 0xf9d2, 0x5997, 0x5a52, 0xfa17, 0x1ad9, 0xba9c, 0xfb54, 0x5b11,
0xbbdf, 0x1b9a, 0x9c45, 0x3c00, 0xdcce, 0x7c8b, 0x3d43, 0x9d06, 0x7dc8, 0xdd8d,
0xde48, 0x7e0d, 0x9ec3, 0x3e86, 0x7f4e, 0xdf0b, 0x3fc5, 0x9f80, 0x20d4, 0x8091,
0x605f, 0xc01a, 0x81d2, 0x2197, 0xc159, 0x611c, 0x62d9, 0xc29c, 0x2252, 0x8217,
0xc3df, 0x639a, 0x8354, 0x2311, 0xa4ce, 0x048b, 0xe445, 0x4400, 0x05c8, 0xa58d,
0x4543, 0xe506, 0xe6c3, 0x4686, 0xa648, 0x060d, 0x47c5, 0xe780, 0x074e, 0xa70b,
0x28e1, 0x88a4, 0x686a, 0xc82f, 0x89e7, 0x29a2, 0xc96c, 0x6929, 0x6aec, 0xcaa9,
0x2a67, 0x8a22, 0xcbea, 0x6baf, 0x8b61, 0x2b24, 0xacfb, 0x0cbe, 0xec70, 0x4c35,
0x0dfd, 0xadb8, 0x4d76, 0xed33, 0xeef6, 0x4eb3, 0xae7d, 0x0e38, 0x4ff0, 0xefb5,
0x0f7b, 0xaf3e, 0x30be, 0x90fb, 0x7035, 0xd070, 0x91b8, 0x31fd, 0xd133, 0x7176,
0x72b3, 0xd2f6, 0x3238, 0x927d, 0xd3b5, 0x73f0, 0x933e, 0x337b, 0xb4a4, 0x14e1,
0xf42f, 0x546a, 0x15a2, 0xb5e7, 0x5529, 0xf56c, 0xf6a9, 0x56ec, 0xb622, 0x1667,
0x57af, 0xf7ea, 0x1724, 0xb761, 0x388b, 0x98ce, 0x7800, 0xd845, 0x998d, 0x39c8,
0xd906, 0x7943, 0x7a86, 0xdac3, 0x3a0d, 0x9a48, 0xdb80, 0x7bc5, 0x9b0b, 0x3b4e,
0xbc91, 0x1cd4, 0xfc1a, 0x5c5f, 0x1d97, 0xbdd2, 0x5d1c, 0xfd59, 0xfe9c, 0x5ed9,
0xbe17, 0x1e52, 0x5f9a, 0xffdf, 0x1f11, 0xbf54},
{0x0000, 0x61b8, 0xe360, 0x82d8, 0xc6c1, 0xa779, 0x25a1, 0x4419, 0xad93, 0xcc2b,
0x4ef3, 0x2f4b, 0x6b52, 0x0aea, 0x8832, 0xe98a, 0x7b37, 0x1a8f, 0x9857, 0xf9ef,
0xbdf6, 0xdc4e, 0x5e96, 0x3f2e, 0xd6a4, 0xb71c, 0x35c4, 0x547c, 0x1065, 0x71dd,
0xf305, 0x92bd, 0xf66e, 0x97d6, 0x150e, 0x74b6, 0x30af, 0x5117, 0xd3cf, 0xb277,
0x5bfd, 0x3a45, 0xb89d, 0xd925, 0x9d3c, 0xfc84, 0x7e5c, 0x1fe4, 0x8d59, 0xece1,
0x6e39, 0x0f81, 0x4b98, 0x2a20, 0xa8f8, 0xc940, 0x20ca, 0x4172, 0xc3aa, 0xa212,
0xe60b, 0x87b3, 0x056b, 0x64d3, 0xecdd, 0x8d65, 0x0fbd, 0x6e05, 0x2a1c, 0x4ba4,
0xc97c, 0xa8c4, 0x414e, 0x20f6, 0xa22e, 0xc396, 0x878f, 0xe637, 0x64ef, 0x0557,
0x97ea, 0xf652, 0x748a, 0x1532, 0x512b, 0x3093, 0xb24b, 0xd3f3, 0x3a79, 0x5bc1,
0xd919, 0xb8a1, 0xfcb8, 0x9d00, 0x1fd8, 0x7e60, 0x1ab3, 0x7b0b, 0xf9d3, 0x986b,
0xdc72, 0xbdca, 0x3f12, 0x5eaa, 0xb720, 0xd698, 0x5440, 0x35f8, 0x71e1, 0x1059,
0x9281, 0xf339, 0x6184, 0x003c, 0x82e4, 0xe35c, 0xa745, 0xc6fd, 0x4425, 0x259d,
0xcc17, 0xadaf, 0x2f77, 0x4ecf, 0x0ad6, 0x6b6e, 0xe9b6, 0x880e, 0xf9ab, 0x9813,
0x1acb, 0x7b73, 0x3f6a, 0x5ed2, 0xdc0a, 0xbdb2, 0x5438, 0x3580, 0xb758, 0xd6e0,
0x92f9, 0xf341, 0x7199, 0x1021, 0x829c, 0xe324, 0x61fc, 0x0044, 0x445d, 0x25e5,
0xa73d, 0xc685, 0x2f0f, 0x4eb7, 0xcc6f, 0xadd7, 0xe9ce, 0x8876, 0x0aae, 0x6b16,
0x0fc5, 0x6e7d, 0xeca5, 0x8d1d, 0xc904, 0xa8bc, 0x2a64, 0x4bdc, 0xa256, 0xc3ee,
0x4136, 0x208e, 0x6497, 0x052f, 0x87f7, 0xe64f, 0x74f2, 0x154a, 0x9792, 0xf62a,
0xb233, 0xd38b, 0x5153, 0x30eb, 0xd961, 0xb8d9, 0x3a01, 0x5bb9, 0x1fa0, 0x7e18,
0xfcc0, 0x9d78, 0x1576, 0x74ce, 0xf616, 0x97ae, 0xd3b7, 0xb20f, 0x30d7, 0x516f,
0xb8e5, 0xd95d, 0x5b85, 0x3a3d, 0x7e24, 0x1f9c, 0x9d44, 0xfcfc, 0x6e41, 0x0ff9,
0x8d21, 0xec99, 0xa880, 0xc938, 0x4be0, 0x2a58, 0xc3d2, 0xa26a, 0x20b2, 0x410a,
0x0513, 0x64ab, 0xe673, 0x87cb, 0xe318, 0x82a0, 0x0078, 0x61c0, 0x25d9, 0x4461,
0xc6b9, 0xa701, 0x4e8b, 0x2f33, 0xadeb, 0xcc53, 0x884a, 0xe9f2, 0x6b2a, 0x0a92,
0x982f, 0xf997, 0x7b4f, 0x1af7, 0x5eee, 0x3f56, 0xbd8e, 0xdc36, 0x35bc, 0x5404,
0xd6dc, 0xb764, 0xf37d, 0x92c5, 0x101d, 0x71a5},
{0x0000, 0xd347, 0xa68f, 0x75c8, 0x6d0f, 0xbe48, 0xcb80, 0x18c7, 0xda1e, 0x0959,
0x7c91, 0xafd6, 0xb711, 0x6456, 0x119e, 0xc2d9, 0xb43d, 0x677a, 0x12b2, 0xc1f5,
0xd932, 0x0a75, 0x7fbd, 0xacfa, 0x6e23, 0xbd64, 0xc8ac, 0x1beb, 0x032c, 0xd06b,
0xa5a3, 0x76e4, 0x687b, 0xbb3c, 0xcef4, 0x1db3, 0x0574, 0xd633, 0xa3fb, 0x70bc,
0xb265, 0x6122, 0x14ea, 0xc7ad, 0xdf6a, 0x0c2d, 0x79e5, 0xaaa2, 0xdc46, 0x0f01,
0x7ac9, 0xa98e, 0xb149, 0x620e, 0x17c6, 0xc481, 0x0658, 0xd51f, 0xa0d7, 0x7390,
0x6b57, 0xb810, 0xcdd8, 0x1e9f, 0xd0f6, 0x03b1, 0x7679, 0xa53e, 0xbdf9, 0x6ebe,
0x1b76, 0xc831, 0x0ae8, 0xd9af, 0xac67, 0x7f20, 0x67e7, 0xb4a0, 0xc168, 0x122f,
0x64cb, 0xb78c, 0xc244, 0x1103, 0x09c4, 0xda83, 0xaf4b, 0x7c0c, 0xbed5, 0x6d92,
0x185a, 0xcb1d, 0xd3da, 0x009d, 0x7555, 0xa612, 0xb88d, 0x6bca, 0x1e02, 0xcd45,
0xd582, 0x06c5, 0x730d, 0xa04a, 0x6293, 0xb1d4, 0xc41c, 0x175b, 0x0f9c, 0xdcdb,
0xa913, 0x7a54, 0x0cb0, 0xdff7, 0xaa3f, 0x7978, 0x61bf, 0xb2f8, 0xc730, 0x1477,
0xd6ae, 0x05e9, 0x7021, 0xa366, 0xbba1, 0x68e6, 0x1d2e, 0xce69, 0x81fd, 0x52ba,
0x2772, 0xf435, 0xecf2, 0x3fb5, 0x4a7d, 0x993a, 0x5be3, 0x88a4, 0xfd6c, 0x2e2b,
0x36ec, 0xe5ab, 0x9063, 0x4324, 0x35c0, 0xe687, 0x934f, 0x4008, 0x58cf, 0x8b88,
0xfe40, 0x2d07, 0xefde, 0x3c99, 0x4951, 0x9a16, 0x82d1, 0x5196, 0x245e, 0xf719,
0xe986, 0x3ac1, 0x4f09, 0x9c4e, 0x8489, 0x57ce, 0x2206, 0xf141, 0x3398, 0xe0df,
0x9517, 0x4650, 0x5e97, 0x8dd0, 0xf818, 0x2b5f, 0x5dbb, 0x8efc, 0xfb34, 0x2873,
0x30b4, 0xe3f3, 0x963b, 0x457c, 0x87a5, 0x54e2, 0x212a, 0xf26d, 0xeaaa, 0x39ed,
0x4c25, 0x9f62, 0x510b, 0x824c, 0xf784, 0x24c3, 0x3c04, 0xef43, 0x9a8b, 0x49cc,
0x8b15, 0x5852, 0x2d9a, 0xfedd, 0xe61a, 0x355d, 0x4095, 0x93d2, 0xe536, 0x3671,
0x43b9, 0x90fe, 0x8839, 0x5b7e, 0x2eb6, 0xfdf1, 0x3f28, 0xec6f, 0x99a7, 0x4ae0,
0x5227, 0x8160, 0xf4a8, 0x27ef, 0x3970, 0xea37, 0x9fff, 0x4cb8, 0x547f, 0x8738,
0xf2f0, 0x21b7, 0xe36e, 0x3029, 0x45e1, 0x96a6, 0x8e61, 0x5d26, 0x28ee, 0xfba9,
0x8d4d, 0x5e0a, 0x2bc2, 0xf885, 0xe042, 0x3305, 0x46cd, 0x958a, 0x5753, 0x8414,
0xf1dc, 0x229b, 0x3a5c, 0xe91b, 0x9cd3, 0x4f94}
};
static inline uint16_t swaplow(uint16_t crc) {
return
((crc & 0xff) << 8) +
((crc & 0xff00) >> 8);
}
// This code assumes that integers are stored little-endian.
__attribute__((optimize("Ofast")))
static uint16_t crc16ibm_3740_word(uint16_t crc, void const *mem, size_t len) {
unsigned char const *data = mem;
if (data == NULL)
return 0xffff;
while (len && ((ptrdiff_t)data & 0x7)) {
len--;
crc = (crc << 8) ^
table_byte[((crc >> 8) ^ *data++) & 0xff];
}
crc = swaplow(crc);
size_t n = len >> 3;
for (size_t i = 0; i < n; i++) {
uint64_t word = crc ^ ((uint64_t const *)data)[i];
crc = table_word[7][word & 0xff] ^
table_word[6][(word >> 8) & 0xff] ^
table_word[5][(word >> 16) & 0xff] ^
table_word[4][(word >> 24) & 0xff] ^
table_word[3][(word >> 32) & 0xff] ^
table_word[2][(word >> 40) & 0xff] ^
table_word[1][(word >> 48) & 0xff] ^
table_word[0][word >> 56];
}
data += n << 3;
len &= 7;
crc = swaplow(crc);
while (len) {
len--;
crc = (crc << 8) ^
table_byte[((crc >> 8) ^ *data++) & 0xff];
}
return crc;
}
uint16_t crc16(uint8_t const *data, int const length)
{
//Calculate the CRC16 checksum for the specified data block
unsigned short crc = 0;
//Return the calculated checksum
return crc16ibm_3740_word(crc, data, length);
}
/* [] END OF FILE */
/* f_util.c
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#include <assert.h>
#include <stdio.h>
//
#include "ff.h"
const char *FRESULT_str(FRESULT i) {
switch (i) {
case FR_OK:
return "Succeeded";
case FR_DISK_ERR:
return "A hard error occurred in the low level disk I/O layer";
case FR_INT_ERR:
return "Assertion failed";
case FR_NOT_READY:
return "The physical drive cannot work";
case FR_NO_FILE:
return "Could not find the file";
case FR_NO_PATH:
return "Could not find the path";
case FR_INVALID_NAME:
return "The path name format is invalid";
case FR_DENIED:
return "Access denied due to prohibited access or directory full";
case FR_EXIST:
return "Access denied due to prohibited access (exists)";
case FR_INVALID_OBJECT:
return "The file/directory object is invalid";
case FR_WRITE_PROTECTED:
return "The physical drive is write protected";
case FR_INVALID_DRIVE:
return "The logical drive number is invalid";
case FR_NOT_ENABLED:
return "The volume has no work area (mount)";
case FR_NO_FILESYSTEM:
return "There is no valid FAT volume";
case FR_MKFS_ABORTED:
return "The f_mkfs() aborted due to any problem";
case FR_TIMEOUT:
return "Could not get a grant to access the volume within defined "
"period";
case FR_LOCKED:
return "The operation is rejected according to the file sharing "
"policy";
case FR_NOT_ENOUGH_CORE:
return "LFN working buffer could not be allocated";
case FR_TOO_MANY_OPEN_FILES:
return "Number of open files > FF_FS_LOCK";
case FR_INVALID_PARAMETER:
return "Given parameter is invalid";
default:
return "Unknown";
}
}
FRESULT delete_node (
TCHAR* path, /* Path name buffer with the sub-directory to delete */
UINT sz_buff, /* Size of path name buffer (items) */
FILINFO* fno /* Name read buffer */
)
{
UINT i, j;
FRESULT fr;
DIR dir;
fr = f_opendir(&dir, path); /* Open the sub-directory to make it empty */
if (fr != FR_OK) return fr;
for (i = 0; path[i]; i++) ; /* Get current path length */
path[i++] = '/';
for (;;) {
fr = f_readdir(&dir, fno); /* Get a directory item */
if (fr != FR_OK || !fno->fname[0]) break; /* End of directory? */
j = 0;
do { /* Make a path name */
if (i + j >= sz_buff) { /* Buffer over flow? */
fr = 100; break; /* Fails with 100 when buffer overflow */
}
path[i + j] = fno->fname[j];
} while (fno->fname[j++]);
if (fno->fattrib & AM_DIR) { /* Item is a sub-directory */
fr = delete_node(path, sz_buff, fno);
} else { /* Item is a file */
fr = f_unlink(path);
}
if (fr != FR_OK) break;
}
path[--i] = 0; /* Restore the path name */
f_closedir(&dir);
if (fr == FR_OK) fr = f_unlink(path); /* Delete the empty sub-directory */
return fr;
}
void ls(const char *dir) {
char cwdbuf[FF_LFN_BUF] = {0};
FRESULT fr; /* Return value */
char const *p_dir;
if (dir[0]) {
p_dir = dir;
} else {
fr = f_getcwd(cwdbuf, sizeof cwdbuf);
if (FR_OK != fr) {
printf("f_getcwd error: %s (%d)\n", FRESULT_str(fr), fr);
return;
}
p_dir = cwdbuf;
}
printf("Directory Listing: %s\n", p_dir);
DIR dj = {}; /* Directory object */
FILINFO fno = {}; /* File information */
assert(p_dir);
fr = f_findfirst(&dj, &fno, p_dir, "*");
if (FR_OK != fr) {
printf("f_findfirst error: %s (%d)\n", FRESULT_str(fr), fr);
return;
}
while (fr == FR_OK && fno.fname[0]) { /* Repeat while an item is found */
/* Create a string that includes the file name, the file size and the
attributes string. */
const char *pcWritableFile = "writable file",
*pcReadOnlyFile = "read only file",
*pcDirectory = "directory";
const char *pcAttrib;
/* Point pcAttrib to a string that describes the file. */
if (fno.fattrib & AM_DIR) {
pcAttrib = pcDirectory;
} else if (fno.fattrib & AM_RDO) {
pcAttrib = pcReadOnlyFile;
} else {
pcAttrib = pcWritableFile;
}
/* Create a string that includes the file name, the file size and the
attributes string. */
printf("%s [%s] [size=%llu]\n", fno.fname, pcAttrib, fno.fsize);
fr = f_findnext(&dj, &fno); /* Search for next item */
}
f_closedir(&dj);
}
/* ff_stdio.c
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
// For compatibility with FreeRTOS+FAT API
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "my_debug.h"
//
#include "f_util.h"
#include "ff_stdio.h"
#define TRACE_PRINTF(fmt, args...) {}
//#define TRACE_PRINTF printf
static BYTE posix2mode(const char *pcMode) {
if (0 == strcmp("r", pcMode)) return FA_READ;
if (0 == strcmp("r+", pcMode)) return FA_READ | FA_WRITE;
if (0 == strcmp("w", pcMode)) return FA_CREATE_ALWAYS | FA_WRITE;
if (0 == strcmp("w+", pcMode)) return FA_CREATE_ALWAYS | FA_WRITE | FA_READ;
if (0 == strcmp("a", pcMode)) return FA_OPEN_APPEND | FA_WRITE;
if (0 == strcmp("a+", pcMode)) return FA_OPEN_APPEND | FA_WRITE | FA_READ;
if (0 == strcmp("wx", pcMode)) return FA_CREATE_NEW | FA_WRITE;
if (0 == strcmp("w+x", pcMode)) return FA_CREATE_NEW | FA_WRITE | FA_READ;
return 0;
}
int fresult2errno(FRESULT fr) {
switch (fr) {
case FR_OK:
return 0;
case FR_DISK_ERR:
return EIO;
case FR_INT_ERR:
return EIO;
case FR_NOT_READY:
return EIO;
case FR_NO_FILE:
return ENOENT;
case FR_NO_PATH:
return ENOENT;
case FR_INVALID_NAME:
return ENAMETOOLONG;
case FR_DENIED:
return EACCES;
case FR_EXIST:
return EEXIST;
case FR_INVALID_OBJECT:
return EIO;
case FR_WRITE_PROTECTED:
return EACCES;
case FR_INVALID_DRIVE:
return ENOENT;
case FR_NOT_ENABLED:
return ENOENT;
case FR_NO_FILESYSTEM:
return ENOENT;
case FR_MKFS_ABORTED:
return EIO;
case FR_TIMEOUT:
return EIO;
case FR_LOCKED:
return EACCES;
case FR_NOT_ENOUGH_CORE:
return ENOMEM;
case FR_TOO_MANY_OPEN_FILES:
return ENFILE;
case FR_INVALID_PARAMETER:
return ENOSYS;
default:
return -1;
}
}
FF_FILE *ff_fopen(const char *pcFile, const char *pcMode) {
TRACE_PRINTF("%s\n", __func__);
// FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode);
// /* Open or create a file */ FRESULT f_open (
// FIL* fp, /* [OUT] Pointer to the file object structure */
// const TCHAR* path, /* [IN] File name */
// BYTE mode /* [IN] Mode flags */
//);
FIL *fp = malloc(sizeof(FIL));
if (!fp) {
errno = ENOMEM;
return NULL;
}
FRESULT fr = f_open(fp, pcFile, posix2mode(pcMode));
errno = fresult2errno(fr);
if (FR_OK != fr) {
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
free(fp);
fp = 0;
}
return fp;
}
int ff_fclose(FF_FILE *pxStream) {
TRACE_PRINTF("%s\n", __func__);
// FRESULT f_close (
// FIL* fp /* [IN] Pointer to the file object */
//);
FRESULT fr = f_close(pxStream);
if (FR_OK != fr)
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
errno = fresult2errno(fr);
free(pxStream);
if (FR_OK == fr)
return 0;
else
return -1;
}
// Populates an ff_stat_struct with information about a file.
int ff_stat(const char *pcFileName, FF_Stat_t *pxStatBuffer) {
TRACE_PRINTF("%s\n", __func__);
// FRESULT f_stat (
// const TCHAR* path, /* [IN] Object name */
// FILINFO* fno /* [OUT] FILINFO structure */
//);
myASSERT(pxStatBuffer);
FILINFO filinfo;
FRESULT fr = f_stat(pcFileName, &filinfo);
pxStatBuffer->st_size = filinfo.fsize;
if (FR_OK != fr)
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
errno = fresult2errno(fr);
if (FR_OK == fr)
return 0;
else
return -1;
}
size_t ff_fwrite(const void *pvBuffer, size_t xSize, size_t xItems,
FF_FILE *pxStream) {
TRACE_PRINTF("%s\n", __func__);
// FRESULT f_write (
// FIL* fp, /* [IN] Pointer to the file object structure */
// const void* buff, /* [IN] Pointer to the data to be written */
// UINT btw, /* [IN] Number of bytes to write */
// UINT* bw /* [OUT] Pointer to the variable to return number of
// bytes written */
//);
UINT bw = 0;
FRESULT fr = f_write(pxStream, pvBuffer, xSize * xItems, &bw);
if (FR_OK != fr)
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
errno = fresult2errno(fr);
return bw / xSize;
}
size_t ff_fread(void *pvBuffer, size_t xSize, size_t xItems,
FF_FILE *pxStream) {
TRACE_PRINTF("%s\n", __func__);
// FRESULT f_read (
// FIL* fp, /* [IN] File object */
// void* buff, /* [OUT] Buffer to store read data */
// UINT btr, /* [IN] Number of bytes to read */
// UINT* br /* [OUT] Number of bytes read */
//);
UINT br = 0;
FRESULT fr = f_read(pxStream, pvBuffer, xSize * xItems, &br);
if (FR_OK != fr)
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
errno = fresult2errno(fr);
return br / xSize;
}
int ff_chdir(const char *pcDirectoryName) {
TRACE_PRINTF("%s\n", __func__);
// FRESULT f_chdir (
// const TCHAR* path /* [IN] Path name */
//);
FRESULT fr = f_chdir(pcDirectoryName);
if (FR_OK != fr)
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
errno = fresult2errno(fr);
if (FR_OK == fr)
return 0;
else
return -1;
}
char *ff_getcwd(char *pcBuffer, size_t xBufferLength) {
TRACE_PRINTF("%s\n", __func__);
// FRESULT f_getcwd (
// TCHAR* buff, /* [OUT] Buffer to return path name */
// UINT len /* [IN] The length of the buffer */
//);
char buf[ffconfigMAX_FILENAME] = {0};
FRESULT fr = f_getcwd(buf, sizeof buf);
// f_getcwd uses buf as a work area,
// so even though the string is null-terminated,
// bytes near the end might not be nulls.
myASSERT(strlen(buf) < sizeof buf);
if (FR_OK != fr)
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
errno = fresult2errno(fr);
// If the current working directory name was successfully written to
// pcBuffer then pcBuffer is returned. Otherwise NULL is returned.
if (FR_OK == fr) {
if ('/' != buf[0]) {
// Strip off drive prefix:
char *p = strchr(buf, ':');
if (p)
++p;
else
p = buf;
int rc = snprintf(pcBuffer, xBufferLength, "%s", p);
// only when this returned value is non-negative and less than n,
// the string has been completely written
if (!(0 <= rc && (size_t)rc < xBufferLength))
return NULL;
}
return pcBuffer;
} else {
return NULL;
}
}
int ff_mkdir(const char *pcDirectoryName) {
TRACE_PRINTF("%s(pxStream=%s)\n", __func__, pcDirectoryName);
FRESULT fr = f_mkdir(pcDirectoryName);
if (FR_OK != fr && FR_EXIST != fr)
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
errno = fresult2errno(fr);
if (FR_OK == fr || FR_EXIST == fr)
return 0;
else
return -1;
}
int ff_fputc(int iChar, FF_FILE *pxStream) {
// TRACE_PRINTF("%s(iChar=%c,pxStream=%p)\n", __func__, iChar, pxStream);
// FRESULT f_write (
// FIL* fp, /* [IN] Pointer to the file object structure */
// const void* buff, /* [IN] Pointer to the data to be written */
// UINT btw, /* [IN] Number of bytes to write */
// UINT* bw /* [OUT] Pointer to the variable to return number of
// bytes written */
//);
UINT bw = 0;
uint8_t buff[1];
buff[0] = iChar;
FRESULT fr = f_write(pxStream, buff, 1, &bw);
if (FR_OK != fr)
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
errno = fresult2errno(fr);
// On success the byte written to the file is returned. If any other value
// is returned then the byte was not written to the file and the task's
// errno will be set to indicate the reason.
if (1 == bw)
return iChar;
else {
return -1;
}
}
int ff_fgetc(FF_FILE *pxStream) {
// TRACE_PRINTF("%s(pxStream=%p)\n", __func__, pxStream);
// FRESULT f_read (
// FIL* fp, /* [IN] File object */
// void* buff, /* [OUT] Buffer to store read data */
// UINT btr, /* [IN] Number of bytes to read */
// UINT* br /* [OUT] Number of bytes read */
//);
uint8_t buff[1] = {0};
UINT br;
FRESULT fr = f_read(pxStream, buff, 1, &br);
if (FR_OK != fr)
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
errno = fresult2errno(fr);
// On success the byte read from the file system is returned. If a byte
// could not be read from the file because the read position is already at
// the end of the file then FF_EOF is returned.
if (1 == br)
return buff[0];
else
return FF_EOF;
}
int ff_rmdir(const char *pcDirectory) {
TRACE_PRINTF("%s\n", __func__);
// FRESULT f_unlink (
// const TCHAR* path /* [IN] Object name */
//);
FRESULT fr = f_unlink(pcDirectory);
// If the directory was removed successfully then zero is returned. If the
// directory could not be removed then -1 is returned and the task's errno
// is set to indicate the reason.
errno = fresult2errno(fr);
if (FR_OK == fr)
return 0;
else
return -1;
}
int ff_remove(const char *pcPath) {
TRACE_PRINTF("%s\n", __func__);
FRESULT fr = f_unlink(pcPath);
errno = fresult2errno(fr);
if (FR_OK == fr)
return 0;
else
return -1;
}
long ff_ftell(FF_FILE *pxStream) {
TRACE_PRINTF("%s\n", __func__);
// FSIZE_t f_tell (
// FIL* fp /* [IN] File object */
//);
FSIZE_t pos = f_tell(pxStream);
myASSERT(pos < LONG_MAX);
return pos;
}
int ff_fseek(FF_FILE *pxStream, int iOffset, int iWhence) {
TRACE_PRINTF("%s\n", __func__);
FRESULT fr = -1;
switch (iWhence) {
case FF_SEEK_CUR: // The current file position.
if ((int)f_tell(pxStream) + iOffset < 0) return -1;
fr = f_lseek(pxStream, f_tell(pxStream) + iOffset);
break;
case FF_SEEK_END: // The end of the file.
if ((int)f_size(pxStream) + iOffset < 0) return -1;
fr = f_lseek(pxStream, f_size(pxStream) + iOffset);
break;
case FF_SEEK_SET: // The beginning of the file.
if (iOffset < 0) return -1;
fr = f_lseek(pxStream, iOffset);
break;
default:
myASSERT(!"Bad iWhence");
}
errno = fresult2errno(fr);
if (FR_OK == fr)
return 0;
else
return -1;
}
int ff_findfirst(const char *pcDirectory, FF_FindData_t *pxFindData) {
TRACE_PRINTF("%s(%s)\n", __func__, pcDirectory);
// FRESULT f_findfirst (
// DIR* dp, /* [OUT] Poninter to the directory object */
// FILINFO* fno, /* [OUT] Pointer to the file information structure
// */ const TCHAR* path, /* [IN] Pointer to the directory name to be
// opened */ const TCHAR* pattern /* [IN] Pointer to the matching pattern
// string */
//);
char buf1[ffconfigMAX_FILENAME] = {0};
if (pcDirectory[0]) {
FRESULT fr = f_getcwd(buf1, sizeof buf1);
errno = fresult2errno(fr);
if (FR_OK != fr) return -1;
fr = f_chdir(pcDirectory);
errno = fresult2errno(fr);
if (FR_OK != fr) return -1;
}
char buf2[ffconfigMAX_FILENAME] = {0};
FRESULT fr = f_getcwd(buf2, sizeof buf2);
TRACE_PRINTF("%s: f_findfirst(path=%s)\n", __func__, buf2);
fr = f_findfirst(&pxFindData->dir, &pxFindData->fileinfo, buf2, "*");
errno = fresult2errno(fr);
pxFindData->pcFileName = pxFindData->fileinfo.fname;
pxFindData->ulFileSize = pxFindData->fileinfo.fsize;
TRACE_PRINTF("%s: fname=%s\n", __func__, pxFindData->fileinfo.fname);
if (pcDirectory[0]) {
FRESULT fr2 = f_chdir(buf1);
errno = fresult2errno(fr2);
if (FR_OK != fr2) return -1;
}
if (FR_OK == fr)
return 0;
else
return -1;
}
int ff_findnext(FF_FindData_t *pxFindData) {
TRACE_PRINTF("%s\n", __func__);
// FRESULT f_findnext (
// DIR* dp, /* [IN] Poninter to the directory object */
// FILINFO* fno /* [OUT] Pointer to the file information structure
// */
//);
FRESULT fr = f_findnext(&pxFindData->dir, &pxFindData->fileinfo);
errno = fresult2errno(fr);
pxFindData->pcFileName = pxFindData->fileinfo.fname;
pxFindData->ulFileSize = pxFindData->fileinfo.fsize;
TRACE_PRINTF("%s: fname=%s\n", __func__, pxFindData->fileinfo.fname);
if (FR_OK == fr && pxFindData->fileinfo.fname[0]) {
return 0;
} else {
return -1;
}
}
FF_FILE *ff_truncate(const char *pcFileName, long lTruncateSize) {
TRACE_PRINTF("%s\n", __func__);
FIL *fp = malloc(sizeof(FIL));
if (!fp) {
errno = ENOMEM;
return NULL;
}
FRESULT fr = f_open(fp, pcFileName, FA_OPEN_APPEND | FA_WRITE);
if (FR_OK != fr)
EMSG_PRINTF("%s: f_open error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
errno = fresult2errno(fr);
if (FR_OK != fr) return NULL;
while (f_tell(fp) < (FSIZE_t)lTruncateSize) {
UINT bw = 0;
char c = 0;
fr = f_write(fp, &c, 1, &bw);
if (FR_OK != fr)
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
errno = fresult2errno(fr);
if (1 != bw) return NULL;
}
fr = f_lseek(fp, lTruncateSize);
errno = fresult2errno(fr);
if (FR_OK != fr)
EMSG_PRINTF("%s: f_lseek error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
if (FR_OK != fr) return NULL;
fr = f_truncate(fp);
if (FR_OK != fr)
EMSG_PRINTF("%s: f_truncate error: %s (%d)\n", __func__, FRESULT_str(fr),
fr);
errno = fresult2errno(fr);
if (FR_OK == fr)
return fp;
else
return NULL;
}
int ff_seteof(FF_FILE *pxStream) {
TRACE_PRINTF("%s\n", __func__);
FRESULT fr = f_truncate(pxStream);
errno = fresult2errno(fr);
if (FR_OK == fr)
return 0;
else
return FF_EOF;
}
int ff_rename(const char *pcOldName, const char *pcNewName,
int bDeleteIfExists) {
TRACE_PRINTF("%s\n", __func__);
// FRESULT f_rename (
// const TCHAR* old_name, /* [IN] Old object name */
// const TCHAR* new_name /* [IN] New object name */
//);
// Any object with this path name except old_name must not be exist, or the
// function fails with FR_EXIST.
if (bDeleteIfExists) f_unlink(pcNewName);
FRESULT fr = f_rename(pcOldName, pcNewName);
errno = fresult2errno(fr);
if (FR_OK == fr)
return 0;
else
return -1;
}
char *ff_fgets(char *pcBuffer, size_t xCount, FF_FILE *pxStream) {
TRACE_PRINTF("%s\n", __func__);
TCHAR *p = f_gets(pcBuffer, xCount, pxStream);
// On success a pointer to pcBuffer is returned. If there is a read error
// then NULL is returned and the task's errno is set to indicate the reason.
if (p == pcBuffer)
return pcBuffer;
else {
errno = EIO;
return NULL;
}
}
/*
* file_stream.c
*
* Created on: Jun 20, 2024
* Author: carlk
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//
#include "pico/stdlib.h"
//
#include "ff.h"
//
#include "f_util.h"
#include "my_debug.h"
//
#include "file_stream.h"
typedef struct {
FIL file;
} cookie_t;
// functions.read should return -1 on failure, or else the number of bytes read (0 on EOF).
// It is similar to read, except that cookie will be passed as the first argument.
static ssize_t cookie_read_function(void *vcookie_p, char *buf, size_t n) {
cookie_t *cookie_p = vcookie_p;
FIL *file_p = &cookie_p->file;
UINT br;
FRESULT fr = f_read(file_p, buf, n, &br);
if (FR_OK != fr) {
DBG_PRINTF("f_read error: %s\n", FRESULT_str(fr));
return -1;
}
return br;
}
// functions.write should return -1 on failure, or else the number of bytes written.
// It is similar to write, except that cookie will be passed as the first argument.
static ssize_t cookie_write_function(void *vcookie_p, const char *buf, size_t n) {
cookie_t *cookie_p = vcookie_p;
FIL *file_p = &cookie_p->file;
UINT bw;
FRESULT fr = f_write(file_p, buf, n, &bw);
if (FR_OK != fr) {
DBG_PRINTF("f_read error: %s\n", FRESULT_str(fr));
return -1;
}
return bw;
}
// functions.seek should return -1 on failure, and 0 on success,
// with off set to the current file position.
// It is a cross between lseek and fseek, with the whence argument interpreted in the same
// manner.
static int cookie_seek_function(void *vcookie_p, off_t *off, int whence) {
cookie_t *cookie_p = vcookie_p;
FIL *file_p = &cookie_p->file;
FRESULT fr = FR_OK;
switch (whence) {
case SEEK_SET:
fr = f_lseek(file_p, *off);
break;
case SEEK_CUR:
fr = f_lseek(file_p, f_tell(file_p) + *off);
break;
case SEEK_END:
fr = f_lseek(file_p, f_size(file_p) + *off);
break;
default:
ASSERT_CASE_NOT(whence);
}
if (FR_OK != fr) {
DBG_PRINTF("f_lseek error: %s\n", FRESULT_str(fr));
return -1;
} else {
*off = f_tell(file_p);
return 0;
}
}
// functions.close should return -1 on failure, or 0 on success.
// It is similar to close, except that cookie will be passed as the first argument.
// A failed close will still invalidate the stream.
static int cookie_close_function(void *vcookie_p) {
cookie_t *cookie_p = vcookie_p;
FIL *file_p = &cookie_p->file;
FRESULT fr = f_close(file_p);
free(vcookie_p);
if (FR_OK != fr) {
DBG_PRINTF("f_close error: %s\n", FRESULT_str(fr));
return -1;
} else {
return 0;
}
}
FILE *open_file_stream(const char *pathname, const char *pcMode) {
cookie_t *cookie_p = malloc(sizeof(cookie_t));
if (!cookie_p) {
return NULL;
}
BYTE mode = 0;
// POSIX FatFs
if (0 == strcmp("r", pcMode))
mode = FA_READ;
else if (0 == strcmp("r+", pcMode))
mode = FA_READ | FA_WRITE;
else if (0 == strcmp("w", pcMode))
mode = FA_CREATE_ALWAYS | FA_WRITE;
else if (0 == strcmp("w+", pcMode))
mode = FA_CREATE_ALWAYS | FA_WRITE | FA_READ;
else if (0 == strcmp("a", pcMode))
mode = FA_OPEN_APPEND | FA_WRITE;
else if (0 == strcmp("a+", pcMode))
mode = FA_OPEN_APPEND | FA_WRITE | FA_READ;
else if (0 == strcmp("wx", pcMode))
mode = FA_CREATE_NEW | FA_WRITE;
else if (0 == strcmp("w+x", pcMode))
mode = FA_CREATE_NEW | FA_WRITE | FA_READ;
FRESULT fr = f_open(&cookie_p->file, pathname, mode);
if (FR_OK != fr) {
DBG_PRINTF("f_lseek error: %s\n", FRESULT_str(fr));
return NULL;
}
cookie_io_functions_t iofs = {cookie_read_function, cookie_write_function,
cookie_seek_function, cookie_close_function};
/* create the stream */
FILE *file = fopencookie(cookie_p, pcMode, iofs);
if (!file) cookie_close_function(cookie_p);
return (file);
}
/* glue.c
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module SKELETON for FatFs (C)ChaN, 2019 */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various exsisting */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*/
//
//
#include "hw_config.h"
#include "my_debug.h"
#include "sd_card.h"
//
#include "diskio.h" /* Declarations of disk functions */
#define TRACE_PRINTF(fmt, args...)
//#define TRACE_PRINTF printf // task_printf
/*-----------------------------------------------------------------------*/
/* Get Drive Status */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status(BYTE pdrv /* Physical drive number to identify the drive */
) {
TRACE_PRINTF(">>> %s\n", __FUNCTION__);
sd_card_t *sd_card_p = sd_get_by_num(pdrv);
if (!sd_card_p) return RES_PARERR;
sd_card_detect(sd_card_p); // Fast: just a GPIO read
return sd_card_p->state.m_Status; // See http://elm-chan.org/fsw/ff/doc/dstat.html
}
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize(
BYTE pdrv /* Physical drive number to identify the drive */
) {
TRACE_PRINTF(">>> %s\n", __FUNCTION__);
bool ok = sd_init_driver();
if (!ok) return RES_NOTRDY;
sd_card_t *sd_card_p = sd_get_by_num(pdrv);
if (!sd_card_p) return RES_PARERR;
DSTATUS ds = disk_status(pdrv);
if (STA_NODISK & ds)
return ds;
// See http://elm-chan.org/fsw/ff/doc/dstat.html
return sd_card_p->init(sd_card_p);
}
static int sdrc2dresult(int sd_rc) {
switch (sd_rc) {
case SD_BLOCK_DEVICE_ERROR_NONE:
return RES_OK;
case SD_BLOCK_DEVICE_ERROR_UNUSABLE:
case SD_BLOCK_DEVICE_ERROR_NO_RESPONSE:
case SD_BLOCK_DEVICE_ERROR_NO_INIT:
case SD_BLOCK_DEVICE_ERROR_NO_DEVICE:
return RES_NOTRDY;
case SD_BLOCK_DEVICE_ERROR_PARAMETER:
case SD_BLOCK_DEVICE_ERROR_UNSUPPORTED:
return RES_PARERR;
case SD_BLOCK_DEVICE_ERROR_WRITE_PROTECTED:
return RES_WRPRT;
case SD_BLOCK_DEVICE_ERROR_CRC:
case SD_BLOCK_DEVICE_ERROR_WOULD_BLOCK:
case SD_BLOCK_DEVICE_ERROR_ERASE:
case SD_BLOCK_DEVICE_ERROR_WRITE:
default:
return RES_ERROR;
}
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s) */
/*-----------------------------------------------------------------------*/
DRESULT disk_read(BYTE pdrv, /* Physical drive number to identify the drive */
BYTE *buff, /* Data buffer to store read data */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to read */
) {
TRACE_PRINTF(">>> %s\n", __FUNCTION__);
sd_card_t *sd_card_p = sd_get_by_num(pdrv);
if (!sd_card_p) return RES_PARERR;
int rc = sd_card_p->read_blocks(sd_card_p, buff, sector, count);
return sdrc2dresult(rc);
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s) */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
DRESULT disk_write(BYTE pdrv, /* Physical drive number to identify the drive */
const BYTE *buff, /* Data to be written */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
) {
TRACE_PRINTF(">>> %s\n", __FUNCTION__);
sd_card_t *sd_card_p = sd_get_by_num(pdrv);
if (!sd_card_p) return RES_PARERR;
int rc = sd_card_p->write_blocks(sd_card_p, buff, sector, count);
return sdrc2dresult(rc);
}
#endif
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions */
/*-----------------------------------------------------------------------*/
DRESULT disk_ioctl(BYTE pdrv, /* Physical drive number (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
) {
TRACE_PRINTF(">>> %s\n", __FUNCTION__);
sd_card_t *sd_card_p = sd_get_by_num(pdrv);
if (!sd_card_p) return RES_PARERR;
switch (cmd) {
case GET_SECTOR_COUNT: { // Retrieves number of available sectors, the
// largest allowable LBA + 1, on the drive
// into the LBA_t variable pointed by buff.
// This command is used by f_mkfs and f_fdisk
// function to determine the size of
// volume/partition to be created. It is
// required when FF_USE_MKFS == 1.
static LBA_t n;
n = sd_card_p->get_num_sectors(sd_card_p);
*(LBA_t *)buff = n;
if (!n) return RES_ERROR;
return RES_OK;
}
case GET_BLOCK_SIZE: { // Retrieves erase block size of the flash
// memory media in unit of sector into the DWORD
// variable pointed by buff. The allowable value
// is 1 to 32768 in power of 2. Return 1 if the
// erase block size is unknown or non flash
// memory media. This command is used by only
// f_mkfs function and it attempts to align data
// area on the erase block boundary. It is
// required when FF_USE_MKFS == 1.
static DWORD bs = 1;
*(DWORD *)buff = bs;
return RES_OK;
}
case CTRL_SYNC:
sd_card_p->sync(sd_card_p);
return RES_OK;
default:
return RES_PARERR;
}
}
/* my_debug.c
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
//
#if PICO_RP2040
#include "RP2040.h"
#else
#include "RP2350.h"
#endif
#include "pico/stdlib.h"
//
#include "crash.h"
//
#include "my_debug.h"
/* Function Attribute ((weak))
The weak attribute causes a declaration of an external symbol to be emitted as a weak symbol
rather than a global. This is primarily useful in defining library functions that can be
overridden in user code, though it can also be used with non-function declarations. The
overriding symbol must have the same type as the weak symbol.
https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
You can override these functions in your application to redirect "stdout"-type messages.
*/
/* Single string output callbacks */
void __attribute__((weak)) put_out_error_message(const char *s) { (void)s; }
void __attribute__((weak)) put_out_info_message(const char *s) { (void)s; }
void __attribute__((weak)) put_out_debug_message(const char *s) { (void)s; }
/* "printf"-style output callbacks */
#if defined(USE_PRINTF) && USE_PRINTF
int __attribute__((weak))
error_message_printf(const char *func, int line,
const char *fmt, ...)
{
printf("%s:%d: ", func, line);
va_list args;
va_start(args, fmt);
int cw = vprintf(fmt, args);
va_end(args);
stdio_flush();
return cw;
}
int __attribute__((weak)) error_message_printf_plain(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int cw = vprintf(fmt, args);
va_end(args);
stdio_flush();
return cw;
}
int __attribute__((weak)) info_message_printf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int cw = vprintf(fmt, args);
va_end(args);
return cw;
}
int __attribute__((weak))
debug_message_printf(const char *func, int line,
const char *fmt, ...)
{
#if defined(NDEBUG) || !USE_DBG_PRINTF
(void) func;
(void) line;
#endif
va_list args;
va_start(args, fmt);
int cw = vprintf(fmt, args);
va_end(args);
stdio_flush();
return cw;
}
#else
/* These will truncate at 256 bytes. You can tell by checking the return code. */
int __attribute__((weak))
error_message_printf(const char *func, int line,
const char *fmt, ...)
{
char buf[256] = {0};
va_list args;
va_start(args, fmt);
int cw = vsnprintf(buf, sizeof(buf), fmt, args);
put_out_error_message(buf);
va_end(args);
return cw;
}
int __attribute__((weak)) error_message_printf_plain(const char *fmt, ...) {
char buf[256] = {0};
va_list args;
va_start(args, fmt);
int cw = vsnprintf(buf, sizeof(buf), fmt, args);
put_out_info_message(buf);
va_end(args);
return cw;
}
int __attribute__((weak)) info_message_printf(const char *fmt, ...) {
char buf[256] = {0};
va_list args;
va_start(args, fmt);
int cw = vsnprintf(buf, sizeof(buf), fmt, args);
put_out_info_message(buf);
va_end(args);
return cw;
}
int __attribute__((weak))
debug_message_printf(const char *func, int line,
const char *fmt, ...)
{
char buf[256] = {0};
va_list args;
va_start(args, fmt);
int cw = vsnprintf(buf, sizeof(buf), fmt, args);
put_out_debug_message(buf);
va_end(args);
return cw;
}
#endif
void __attribute__((weak)) my_assert_func(const char *file, int line, const char *func,
const char *pred) {
error_message_printf_plain("assertion \"%s\" failed: file \"%s\", line %d, function: %s\n",
pred, file, line, func);
__disable_irq(); /* Disable global interrupts. */
capture_assert(file, line, func, pred);
}
void assert_case_not_func(const char *file, int line, const char *func, int v) {
char pred[128];
snprintf(pred, sizeof pred, "case not %d", v);
my_assert_func(file, line, func, pred);
}
void assert_case_is(const char *file, int line, const char *func, int v, int expected) {
char pred[128];
snprintf(pred, sizeof pred, "%d is %d", v, expected);
my_assert_func(file, line, func, pred);
}
void dump8buf(char *buf, size_t buf_sz, uint8_t *pbytes, size_t nbytes) {
int n = 0;
for (size_t byte_ix = 0; byte_ix < nbytes; ++byte_ix) {
for (size_t col = 0; col < 32 && byte_ix < nbytes; ++col, ++byte_ix) {
n += snprintf(buf + n, buf_sz - n, "%02hhx ", pbytes[byte_ix]);
myASSERT(0 < n && n < (int)buf_sz);
}
n += snprintf(buf + n, buf_sz - n, "\n");
myASSERT(0 < n && n < (int)buf_sz);
}
}
void hexdump_8(const char *s, const uint8_t *pbytes, size_t nbytes) {
IMSG_PRINTF("\n%s(%s, 0x%p, %zu)\n", __FUNCTION__, s, pbytes, nbytes);
stdio_flush();
size_t col = 0;
for (size_t byte_ix = 0; byte_ix < nbytes; ++byte_ix) {
IMSG_PRINTF("%02hhx ", pbytes[byte_ix]);
if (++col > 31) {
IMSG_PRINTF("\n");
col = 0;
}
stdio_flush();
}
}
// nwords is size in WORDS!
void hexdump_32(const char *s, const uint32_t *pwords, size_t nwords) {
IMSG_PRINTF("\n%s(%s, 0x%p, %zu)\n", __FUNCTION__, s, pwords, nwords);
stdio_flush();
size_t col = 0;
for (size_t word_ix = 0; word_ix < nwords; ++word_ix) {
IMSG_PRINTF("%08lx ", pwords[word_ix]);
if (++col > 7) {
IMSG_PRINTF("\n");
col = 0;
}
stdio_flush();
}
}
// nwords is size in bytes
bool compare_buffers_8(const char *s0, const uint8_t *pbytes0, const char *s1,
const uint8_t *pbytes1, const size_t nbytes) {
/* Verify the data. */
if (0 != memcmp(pbytes0, pbytes1, nbytes)) {
hexdump_8(s0, pbytes0, nbytes);
hexdump_8(s1, pbytes1, nbytes);
return false;
}
return true;
}
// nwords is size in WORDS!
bool compare_buffers_32(const char *s0, const uint32_t *pwords0, const char *s1,
const uint32_t *pwords1, const size_t nwords) {
/* Verify the data. */
if (0 != memcmp(pwords0, pwords1, nwords * sizeof(uint32_t))) {
hexdump_32(s0, pwords0, nwords);
hexdump_32(s1, pwords1, nwords);
return false;
}
return true;
}
/* [] END OF FILE */
/* my_rtc.c
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#include <assert.h>
#include <stdbool.h>
#include <time.h>
//
#include "pico/aon_timer.h"
#include "pico/stdio.h"
#include "pico/stdlib.h"
#include "pico/util/datetime.h"
#if HAS_RP2040_RTC
# include "hardware/rtc.h"
#endif
//
#include "crc.h"
#include "ff.h"
//
#include "my_rtc.h"
time_t epochtime;
// Make an attempt to save a recent time stamp across reset:
typedef struct rtc_save {
uint32_t signature;
struct timespec ts;
char checksum; // last, not included in checksum
} rtc_save_t;
static rtc_save_t rtc_save __attribute__((section(".uninitialized_data")));
static bool get_time(struct timespec *ts) {
if (!aon_timer_is_running()) return false;
aon_timer_get_time(ts);
return true;
}
/**
* @brief Update the epochtime variable from the always-on timer
*
* If the always-on timer is running, copy its current value to the epochtime variable.
* Also, copy the current always-on timer value to the rtc_save structure and
* calculate a checksum of the structure.
*/
static void update_epochtime() {
bool ok = get_time(&rtc_save.ts);
if (!ok) return;
// Set the signature to the magic number
rtc_save.signature = 0xBABEBABE;
// Calculate the checksum of the structure
rtc_save.checksum = crc7((uint8_t *)&rtc_save, offsetof(rtc_save_t, checksum));
// Copy the seconds part of the always-on timer to the epochtime variable
epochtime = rtc_save.ts.tv_sec;
}
/**
* @brief Get the current time in seconds since the Epoch.
* *
* @param[in] pxTime If not NULL, the current time is copied here.
* @return The current time in seconds since the Epoch.
*/
time_t time(time_t *pxTime) {
update_epochtime();
if (pxTime) {
*pxTime = epochtime;
}
return epochtime;
}
/**
* @brief Initialize the always-on timer and save its value to the rtc_save structure
*
* If the always-on timer is already running, this function does nothing.
* Otherwise, it initializes the RTC if it is available and checks if the saved
* time is valid. If the saved time is valid, it sets the always-on timer to the saved
* value.
*/
void time_init() {
// If the always-on timer is already running, return immediately
if (aon_timer_is_running()) return;
// Initialize the RTC if it is available
#if HAS_RP2040_RTC
rtc_init();
#endif
// Check if the saved time is valid
char xor_checksum = crc7((uint8_t *)&rtc_save, offsetof(rtc_save_t, checksum));
bool ok = rtc_save.signature == 0xBABEBABE && rtc_save.checksum == xor_checksum;
// If the saved time is valid, set the always-on timer
if (ok) aon_timer_set_time(&rtc_save.ts);
}
/**
* @brief Get the current time in the FAT time format
*
* The FAT file system uses a specific date and time format that is different
* from the one used by the standard C library. This function converts the
* current time to the FAT time format.
*
* @return The current time in the FAT time format (DWORD)
*/
DWORD get_fattime(void) {
struct timespec ts;
bool ok = get_time(&ts);
if (!ok) return 0;
struct tm t;
localtime_r(&ts.tv_sec, &t);
DWORD fattime = 0;
// bit31:25
// Year origin from the 1980 (0..127, e.g. 37 for 2017)
// tm_year int years since 1900
int yr = t.tm_year + 1900 - 1980;
assert(yr >= 0 && yr <= 127);
fattime |= (0b01111111 & yr) << 25;
// bit24:21
// Month (1..12)
// tm_mon int months since January 0-11
uint8_t mo = t.tm_mon + 1;
assert(mo >= 1 && mo <= 12);
fattime |= (0b00001111 & mo) << 21;
// bit20:16
// Day of the month (1..31)
// tm_mday int day of the month 1-31
uint8_t da = t.tm_mday;
assert(da >= 1 && da <= 31);
fattime |= (0b00011111 & da) << 16;
// bit15:11
// Hour (0..23)
// tm_hour int hours since midnight 0-23
uint8_t hr = t.tm_hour;
assert(hr >= 0 && hr <= 23);
fattime |= (0b00011111 & hr) << 11;
// bit10:5
// Minute (0..59)
// tm_min int minutes after the hour 0-59
uint8_t mi = t.tm_min;
assert(mi >= 0 && mi <= 59);
fattime |= (0b00111111 & mi) << 5;
// bit4:0
// Second / 2 (0..29, e.g. 25 for 50)
// tm_sec int seconds after the minute 0-61*
uint8_t sd = t.tm_sec / 2;
assert(sd >= 0 && sd <= 30); // The extra range is to accommodate for leap seconds
fattime |= (0b00011111 & sd);
return fattime;
}
/* rtc.c
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#include <time.h>
//
#include "pico/aon_timer.h"
#include "pico/stdio.h"
#include "pico/stdlib.h"
#include "pico/util/datetime.h"
//
#include "ff.h"
#include "util.h" // calculate_checksum
//
#include "rtc.h"
time_t epochtime;
// Make an attempt to save a recent time stamp across reset:
typedef struct rtc_save {
uint32_t signature;
struct timespec ts;
uint32_t checksum; // last, not included in checksum
} rtc_save_t;
static rtc_save_t rtc_save __attribute__((section(".uninitialized_data")));
static void update_epochtime() {
struct tm timeinfo;
aon_timer_get_time(&rtc_save.ts);
rtc_save.signature = 0xBABEBABE;
localtime_r(&rtc_save.ts.tv_sec, &timeinfo);
rtc_save.checksum = calculate_checksum((uint32_t *)&rtc_save,
offsetof(rtc_save_t, checksum));
epochtime = mktime(&timeinfo);
// configASSERT(-1 != epochtime);
}
time_t time(time_t *pxTime) {
update_epochtime();
if (pxTime) {
*pxTime = epochtime;
}
return epochtime;
}
void time_init() {
struct timespec ts;
aon_timer_get_time(&ts);
struct tm t, t2;
localtime_r(&ts.tv_sec, &t);
localtime_r(&rtc_save.ts.tv_sec, &t2);
if (t.tm_year != t2.tm_year) {
uint32_t xor_checksum = calculate_checksum(
(uint32_t *)&rtc_save, offsetof(rtc_save_t, checksum));
if (rtc_save.signature == 0xBABEBABE &&
rtc_save.checksum == xor_checksum) {
// Set rtc
aon_timer_set_time(&rtc_save.ts);
}
}
}
// Called by FatFs:
DWORD get_fattime(void) {
struct timespec ts;
aon_timer_get_time(&ts);
struct tm t;
localtime_r(&ts.tv_sec, &t);
DWORD fattime = 0;
// bit31:25
// Year origin from the 1980 (0..127, e.g. 37 for 2017)
uint8_t yr = t.tm_year - 1980;
fattime |= (0b01111111 & yr) << 25;
// bit24:21
// Month (1..12)
uint8_t mo = t.tm_mon;
fattime |= (0b00001111 & mo) << 21;
// bit20:16
// Day of the month (1..31)
uint8_t da = t.tm_mday;
fattime |= (0b00011111 & da) << 16;
// bit15:11
// Hour (0..23)
uint8_t hr = t.tm_hour;
fattime |= (0b00011111 & hr) << 11;
// bit10:5
// Minute (0..59)
uint8_t mi = t.tm_min;
fattime |= (0b00111111 & mi) << 5;
// bit4:0
// Second / 2 (0..29, e.g. 25 for 50)
uint8_t sd = t.tm_sec / 2;
fattime |= (0b00011111 & sd);
return fattime;
}
#include "sd_timeouts.h"
sd_timeouts_t sd_timeouts __attribute__((weak)) = {
.sd_command = 2000, // Timeout in ms for response
.sd_command_retries = 3, // Times SPI cmd is retried when there is no response
.sd_lock = 8000, // Timeout in ms for response
.sd_spi_read = 1000, // Timeout in ms for response
.sd_spi_write = 1000, // Timeout in ms for response
.sd_spi_write_read = 1000, // Timeout in ms for response
.spi_lock = 4000, // Timeout in ms for response
.rp2040_sdio_command_R1 = 5, // Timeout in ms for response
.rp2040_sdio_command_R2 = 2, // Timeout in ms for response
.rp2040_sdio_command_R3 = 2, // Timeout in ms for response
.rp2040_sdio_rx_poll = 1000, // Timeout in ms for response
.rp2040_sdio_tx_poll = 5000, // Timeout in ms for response
.sd_sdio_begin = 1000, // Timeout in ms for response
.sd_sdio_stopTransmission = 200, // Timeout in ms for response
};
/*
* util.c
*
* Created on: Apr 12, 2022
* Author: carlk
*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
//
#include "util.h"
int gcd(int a,int b) {
int R;
while ((a % b) > 0) {
R = a % b;
a = b;
b = R;
}
return b;
}
char const* uint8_binary_str(uint8_t number) {
static char b[sizeof number * 8 + 1];
memset(b, 0, sizeof b);
for (size_t i = 0; i < sizeof number * 8; ++i) {
unsigned int mask = 1 << i;
if (number & mask)
b[sizeof number * 8 - 1 - i] = '1';
else
b[sizeof number * 8 - 1 - i] = '0';
}
return b;
}
char const* uint_binary_str(unsigned int number) {
static char b[sizeof number * 8 + 1];
memset(b, 0, sizeof b);
for (size_t i = 0; i < sizeof number * 8; ++i) {
unsigned int mask = 1 << i;
if (number & mask)
b[sizeof number * 8 - 1 - i] = '1';
else
b[sizeof number * 8 - 1 - i] = '0';
}
return b;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment