想用stm8写一个模拟三线spi读取寄存器的程序,时钟是8M的,所以没用延时,环境是IAR,三线spi以及需要读取的寄存器地址如下;
下面是自己修改的一点程序,不能运行,可以自行删除修改
```c
#include "stm8s.h"
#include "stdio.h"
#include "iostm8s105k4.h"
#define CSN PC_ODR_ODR6
#define SCK PC_ODR_ODR5
#define MISO PC_ODR_ODR7
#ifdef _RAISONANCE_
#define PUTCHAR_PROTOTYPE int putchar (char c)
#define GETCHAR_PROTOTYPE int getchar (void)
#elif defined (_COSMIC_)
#define PUTCHAR_PROTOTYPE char putchar (char c)
#define GETCHAR_PROTOTYPE char getchar (void)
#else /* _IAR_ */
#define PUTCHAR_PROTOTYPE int putchar (int c)
#define GETCHAR_PROTOTYPE int getchar (void)
#endif /* _RAISONANCE_ */
void GPIO_init(void)
{
PE_DDR = (1<<5); // 配置PE端口的方向寄存器PE5输出
PE_CR1 = (1<<5); // 设置PE5为推挽输出
PC_DDR = 0xe0;//(1<<5);//设置PC端口的方向寄存器PC5,6,7输出
PC_CR1 = 0xe0;//(1<<5);//设置PC5,6,7为推挽输出
}
uint8_t spi_transfer(uint8_t data)
{
// 这里简单地输出一下传输的数据
printf("SPI transfer: 0x%!X(MISSING)\n", data);
// 返回读取的数据
return 0xA5; // 假设读取到的数据为0xA5
}
void READ(void){
int i;
uint8_t addr = 0x10; // 寄存器地址
uint8_t data; // 收到的数据
CSN=0;
for(i=0;i<=15;i++){
SCK=1;
//MISO=1;
//uint8_t cmd = 0x00000011; // 命令格式:[1][1][0][0][A2][A1][A0][R/W]
// spi_transfer(cmd);
// spi_transfer(addr);
SCK=0;
// MISO=0;
// 读取收到的数据
//data = spi_transfer(0); // 发送一个空数据,读取收到的数据
//printf("Received data: 0x%!!(MISSING)X(MISSING)\n", data);
//return 0;
//spi_send_byte(0x55); // 发送数据
}
CSN=1;
}
void TIM1_init(void)
{
TIM1_PSCRH = 0x1F; // 8M系统时钟经预分频f=fck/(PSCR+1)
TIM1_PSCRL = 0x3F; // PSCR=0x1F3F,f=8M/(0x1F3F+1)=1000Hz,每个计数周期1ms
TIM1_ARRH = 0x07; // 自动重载寄存器ARR=0x01F4=500,0x01*0xF4=500
TIM1_ARRL = 0xF4; // 每记数500次产生一次中断,即500ms
TIM1_IER = 0x01; // 允许更新中断
TIM1_CR1 = 0x01; // 计数器使能,开始计数
}
void main(void)
{
//char ans;
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
UART2_DeInit();
UART2_Init((uint32_t)115200, UART2_WORDLENGTH_8D, UART2_STOPBITS_1, UART2_PARITY_NO,
UART2_SYNCMODE_CLOCK_DISABLE, UART2_MODE_TXRX_ENABLE);
printf("\n\rUART2 Example :retarget the C library printf()/getchar() functions to the UART\n\r");
printf("\n\rEnter Text\n\r");
GPIO_init();
TIM1_init();
asm("rim"); // 开全局中断
while (1)
{
READ();
//ans = getchar();
//printf("%c", ans);
}
}
PUTCHAR_PROTOTYPE
{
/* Write a character to the UART2 */
UART2_SendData8(c);
/* Loop until the end of transmission */
while (UART2_GetFlagStatus(UART2_FLAG_TXE) == RESET);
return (c);
}
GETCHAR_PROTOTYPE
{
#ifdef _COSMIC_
char c = 0;
#else
int c = 0;
#endif
while (UART2_GetFlagStatus(UART2_FLAG_RXNE) == RESET);
c = UART2_ReceiveData8();
return (c);
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{
while (1)
{
}
}
#endif
该回答引用ChatGPTc
// 通过SPI发送一个字节并返回所读到的字节
uint8_t spi_transfer(uint8_t data) {
uint8_t incoming = 0;
// 先输出起始边沿,然后每次将一个bit发送出去
SCK = 0;
for(int i=0; i<8; i++){
uint8_t bit = (data << i) & 0x80;
MISO = (bit == 0) ? 0 : 1;
SCK = 1;
incoming |= MISO << (7-i);
SCK = 0;
}
// 返回读取的数据
return incoming;
}
// 读取一个寄存器的内容
void READ(uint8_t regAddr) {
CSN = 0; // CSN拉低表示数据传输开始
// 按照寄存器读取命令格式,将命令、寄存器地址、读写位(0表示读)发送出去
uint8_t cmd = 0x03; // 命令格式:[0][0][1][1][A2][A1][A0][R/W]
uint8_t data = spi_transfer(cmd);
data = spi_transfer(regAddr);
data = spi_transfer(0x00);
CSN = 1; // 数据传输结束,CSN拉高
printf("Received data: 0x%x
", data);
}
你少定义了个MOSI吧?
根据你提供的信息,你要使用3线spi,MSB先行,CPOL=0.CPHA=1,是属于mode 1。给你一些代码片段进行参考,由于没有必要的硬件环境测试,下面的代码没有经过测试,仅供参考:
// 使用软件模拟的方式发送SPI数据的函数
// 参数:
// data: 要发送的数据数组指针
// len: 要发送的数据长度(字节数)
void spi_write(uint8_t* data, uint8_t len) {
for (int i = 0; i < len; i++) { // 对每个字节循环
for (int j = 7; j >= 0; j--) { // 对每个位循环,MSB先行
SCK = 0; // 拉低时钟线
if (data[i] & (1 << j)) { // 判断当前位是否为1
MOSI = 1; // 发送1
} else {
MOSI = 0; // 发送0
}
SCK = 1; // 拉高时钟线,上升沿采样数据
}
}
}
// 使用软件模拟的方式接收SPI数据的函数
// 参数:
// data: 存储接收到的数据的数组指针
// len: 要接收的数据长度(字节数)
void spi_read(uint8_t* data, uint8_t len) {
for (int i = 0; i < len; i++) { // 对每个字节循环
data[i] = 0; // 清零当前字节
for (int j = 7; j >= 0; j--) { // 对每个位循环,MSB先行
SCK = 0; // 拉低时钟线,准备发送空字节
MOSI = 0; // 发送0
SCK = 1; // 拉高时钟线,下降沿采样数据
if (MISO) { // 判断当前位是否为1
data[i] |= (1 << j); // 将当前位设为1
}
}
}
}
// 用于通过SPI协议读取目标寄存器的数据的函数
// 参数:
// reg_addr: 寄存器地址,范围0x00-0xFF
// range: 范围数,范围0-15
// 返回值:
// 一个指向存储读取到的数据的数组的指针,数组长度为(range + 1) * 2 1个
uint8_t* spi_read_reg(uint8_t reg_addr, uint8_t range) {
// 检查参数是否有效
if (reg_addr > 0xFF || range > 15) {
return NULL; // 返回空指针表示错误
}
// 创建一个数组来存储读取到的数据
uint8_t* data = (uint8_t*)malloc((range + 1) * 2);
if (data == NULL) {
return NULL; // 返回空指针表示内存分配失败
}
// 构造Command_Word,bit15为1表示读,bit12-bit14为0,bit11-bit4为寄存器地址,bit3-bit0为范围数
uint8_t command[2];
command[0] = 0x80 | (reg_addr >> 4); // 高字节,MSB先行
command[1] = (reg_addr << 4) | range; // 低字节,MSB先行
// 开始SPI通讯
CSN = 0; // 拉低CS引脚选择从设备
spi_write(command, 2); // 发送Command_Word
spi_read(data, (range + 1) * 2); // 接收Data_Word
CSN = 1; // 拉高CS引脚结束通讯
return data; // 返回数据数组指针
}
void main(void)
{
// 必要的硬件初始化
// 外设初始化
#define REG_ADDR 0x01
#define RANGE 2
// 调用spi_read_reg函数,传入寄存器地址,范围数和CS引脚号
uint8_t* data = spi_read_reg(REG_ADDR, RANGE);
// 检查返回值是否为空指针
if (data == NULL) {
printf("SPI read error!\n");
return -1;
}
// 处理读取到的数据,例如打印出来
printf("SPI read data:\n");
for (int i = 0; i < RANGE + 1; i++) {
// 将两个字节合并为一个16位数据
uint16_t value = (data[i * 2] << 8) | data[i * 2 + 1];
printf("Data_Word %d: %04X\n", i, value);
}
// 释放数据数组占用的内存
free(data);
while (1)
{
}
}
1.使用逻辑分析仪来观察SPI线上的信号波形,检查时序和数据是否符合你要求。
2.在一些必要的地方进行printf打印调试。
祝您好运,早日调通~~
以下答案由GPT-3.5大模型与博主波罗歌共同编写:
首先需要了解一下SPI通信的时序和流程:
SPI总线上每次通信都由一个主设备和一个或多个从设备组成。主设备和从设备之间进行数据传输时需要一个时钟信号(SCK),以及三根传输线:主设备输出(MOSI)、主设备输入(MISO)以及从设备选择(SS)。在SPI总线上,同一时刻只有一个主设备可以选择一个从设备进行通信。SPI通信时序如下图所示:
SPI信号时序采用上升沿采样,下降沿改变数据,因此在代码编写时需要注意时钟和数据信号的正负电平的控制。
以下是参考代码,注释中有详细的解释:
#include "stm8s.h"已黑化的小白 #include "stdio.h"已黑化的小白
#define CSN PC_ODR_ODR6
#define SCK PC_ODR_ODR5
#define MISO PC_IDR_IDR7
void GPIO_init(void)
{
PE_DDR = (1 << 5); // 配置PE端口的方向寄存器PE5输出
PE_CR1 = (1 << 5); // 设置PE5为推挽输出
PC_DDR = 0xe0; // 设置PC5,6,7为输出,即SCK、CSN、MISO
PC_CR1 = 0xe0; // 设置PC5,6,7为推挽输出
}
/**
* @brief SPI发送一个字节,并读取收到的字节
*
* @param data 要发送的数据
* @return uint8_t 收到的数据
*/
uint8_t spi_transfer(uint8_t data)
{
uint8_t i = 0;
uint8_t received_data = 0;
// 发送数据
for (i = 0; i < 8; i++) {
// 先输出数据最高位
if (data & 0x80) {
PE_ODR |= (1 << 5); // 发送高电平
} else {
PE_ODR &= (~(1 << 5)); // 发送低电平
}
data <<= 1; // 左移1位,处理下一位数据
// 发送时钟信号(上升沿)(注意SPI时序采用上升沿采样,下降沿改变数据)
SCK = 1;
SCK = 0;
// 接收数据
received_data <<= 1; // 左移1位,等待下一位数据
if (MISO == 1) {
received_data |= 0x01; // 如果MISO线上是高电平,则设置为1
}
}
return received_data; // 返回接收到的数据
}
/**
* @brief 读取寄存器
*
* @param addr 寄存器地址
* @return uint8_t 寄存器值
*/
uint8_t READ(uint8_t addr)
{
uint8_t value = 0;
CSN = 0; // 片选信号低电平有效
// 发送命令和地址
spi_transfer(0x0F); // 读取命令
spi_transfer(addr); // 寄存器地址
// 读取收到的值
value = spi_transfer(0); // 发送一个空数据,读取收到的数据
CSN = 1; // 片选信号高电平禁止
return value;
}
void TIM1_init(void)
{
TIM1_PSCRH = 0x1F; // 8M系统时钟经预分频f=fck/(PSCR+1)
TIM1_PSCRL = 0x3F; // PSCR=0x1F3F,f=8M/(0x1F3F+1)=1000Hz,每个计数周期1ms
TIM1_ARRH = 0x07; // 自动重载寄存器ARR=0x01F4=500,0x01*0xF4=500
TIM1_ARRL = 0xF4; // 每记数500次产生一次中断,即500ms
TIM1_IER = 0x01; // 允许更新中断
TIM1_CR1 = 0x01; // 计数器使能,开始计数
}
void main(void)
{
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
UART2_DeInit();
UART2_Init((uint32_t)115200, UART2_WORDLENGTH_8D, UART2_STOPBITS_1, UART2_PARITY_NO,
UART2_SYNCMODE_CLOCK_DISABLE, UART2_MODE_TXRX_ENABLE);
printf("\n\rUART2 Example :retarget the C library printf()/getchar() functions to the UART\n\r");
printf("\n\rEnter Text\n\r");
GPIO_init();
TIM1_init();
asm("rim"); // 开全局中断
while (1) {
uint8_t value = READ(0x10);
printf("Read value: 0x%02X\n", value);
}
}
PUTCHAR_PROTOTYPE
{
/* Write a character to the UART2 */
UART2_SendData8(c);
/* Loop until the end of transmission */
while (UART2_GetFlagStatus(UART2_FLAG_TXE) == RESET);
return (c);
}
/**
* @brief 用于通过UART2接收数据
*
* @return int 返回接收到的字符
*/
GETCHAR_PROTOTYPE
{
#ifdef _COSMIC_
char c = 0;
#else
int c = 0;
#endif
while (UART2_GetFlagStatus(UART2_FLAG_RXNE) == RESET);
c = UART2_ReceiveData8();
return (c);
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{
while (1)
{
}
}
#endif
需要注意的一些点:
使用CPOL和CPHA位,能够组合成四种可能的时序关系。CPOL(时钟极性)位控制在没有数据传输时时钟的空闲状态电平,此位对主模式和从模式下的设备都有效。如果CPOL被清’0’,SCK引脚在空闲状态保持低电平;如果CPOL被置’1’,SCK引脚在空闲状态保持高电平。
如果CPHA(时钟相位)位被置’1’,SCK时钟的第二个边沿(CPOL位为0时就是下降沿,CPOL位为1时就是上升沿)进行最高数据位的采样,数据在第一个时钟传输周期被锁存。如果CPHA位被清’0’,SCK时钟的第一边沿(CPOL位为0时就是下降沿,CPOL位为1时就是上升沿)进行数据位采样,数据在第二个时钟传输周期被锁存。
CPOL时钟极性和CPHA时钟相位的组合选择数据捕捉的时钟边沿。下图显示了SPI传输的4种CPHA和CPOL位选择不同的组合主设备与从设备的SCK,MISO,MOSI引脚连接时这些管脚上的时序。