Por lo tanto, estoy tratando de recibir datos seriales síncronos de un DS1306 en modo de 3 cables usando modo 0 de mi AT89LP51ED2 's USART. Sin embargo, mi rutina de lectura en serie está devolviendo todos los 1s en lugar de los datos válidos, aunque los datos válidos parecen estar en el bus serie de acuerdo con lo que veo en mi osciloscopio, el oscilador de 32 kHz funciona normalmente, y Vdd y Vbat están presentes. Sin embargo, escribir parece estar bien, aunque es bastante difícil saberlo con este problema de lectura. También hay un NHD-0216K3Z-FL-GBW módulo LCD en el bus configurado en modo de 3 cables / SPI. Saca la línea serial con 10k & ohm ;, y escribir en ella también funciona, usando una señal de selección de chip independiente.
¿Por qué sucede esto y cómo lo arreglo?
Marcodeserie(estamosleyendolosregistrosdehora/fechade0x0a0x6):
Bytededirección:
Primerbytededatos:
Segundobytededatos:
Tercerbytededatos:
Cuartobytededatos:
Quintobytededatos:
Sextobytededatos:
Séptimobytededatos:
Primerplanorepresentativodevanguardia(tomadodelprimerbyte):
Primerplanorepresentativodelbordeposterior(tambiéntomadodelprimerbyte):
Código,usandoSDCCparael8051(elmain()
esunMWEmain()
altamentesimplificado,elrestoesdirectodesdeelfirmwareconalgunaspiezasirrelevanteseliminadasycomentariosagregadosen):
#include<stdint.h>#include<stdio.h>#include<ctype.h>#include<stdnoreturn.h>#include<stdbool.h>#include<string.h>voidgpio_init();voidserial_init();voidlcd_init();voidread_and_print_rtc();//Peripheralstuff#include<at89c51ed2.h>__sfr__at(0xE6)P0M0;__sfr__at(0xE7)P0M1;__sfr__at(0xD6)P1M0;__sfr__at(0xD7)P1M1;__sfr__at(0xCE)P2M0;__sfr__at(0xCF)P2M1;__sfr__at(0xC6)P3M0;__sfr__at(0xC7)P3M1;__sfr__at(0xBE)P4M0;__sfr__at(0xBF)P4M1;__sfr__at(0x87)PCON;#defineSMOD10x80#defineSMOD00x40noreturnintmain(){gpio_init();serial_init();lcd_init();while(true)read_and_print_rtc();}typedefunion{uint8_tbuf[7];struct{uint8_tsc;uint8_tmi;uint8_thr;uint8_twd;uint8_tdy;uint8_tmo;uint8_tyr;}tm;}rw_time_t;rw_time_ttb;voidrtc_input(uint8_taddr,uint8_t*chars,intn);voidread_and_print_rtc(void){memset(tb.buf,0,sizeof(tb));//Readthetime&dateinonegotoavoidrolloverwoesrtc_input(0x00,tb.buf,7);//debugdumpoutputprintf("\f%x%x%x", tb.buf[0], tb.buf[1], tb.buf[2]);
printf("\n%x%x%x", tb.buf[4], tb.buf[5], tb.buf[6]);
}
void gpio_init(void) __reentrant
{
// We set up the ports as follows:
// P0: quasi-bidir, default FF
// P1: quasi-bidir, default FF
// P2: totem pole, default 0
// P3.0: quasi-bidir, default 1
// P3.1: totem pole, default 1
// P3.2: input only
// P3.3-P3.5: totem pole, default 0
// P3.6-P3.7: totem pole, default 1
// P4.4-P4.7: totem pole, default 1
// Everything starts up in quasi-bidir as TRIPORT is cleared
// Our port latches also start up as 0xFF as well
P0 = 0xFF;
P1 = 0xFF;
P2 = 0x0;
P3 = 0xC7;
P4 = 0xFF;
P0M0 = 0x0;
P0M1 = 0x0;
P1M0 = 0x0;
P1M1 = 0x0;
P2M0 = 0x0;
P2M1 = 0xFF;
P3M0 = 0x04;
P3M1 = 0xFA;
P4M0 = 0x0;
P4M1 = 0xFF;
}
void serial_init(void)
{
// We put the 8051 USART in Mode 0 for this as we are talking to
// SPI-ish devices (DS1306, SPI-ish LCD) -- SMOD0 in PCON is off
// by default, which is good, because we don't need FE anyway;
// however, we need to set SMOD1 which not only means the BRG runs
// at 2x speed but also makes the Rx sample on the rising edge of
// the serial clock when SM2=1 for driving the RTC
PCON |= SMOD1;
// SM2 is also set to 0 by default, which means the clock idles HIGH
// This is what the LCD expects, but the RTC expects idle LOW --
// writing to the USART requires a CPOL (SM2) flip before driving SS
// We set up the BRG for an 83kHz clock rate -- its the fastest
// rate we can get from the BRG that meets the 100kHz max on the LCD
BRL = 196;
BDRCON = BRR | TBCK | RBCK | SPD | SRC;
}
const int MAXCOLS = 16;
const int MAXROWS = 2;
void _move_cursor(int r, int c);
void _visual_bell(void);
void lcd_output(uint8_t* chars, int n);
#define CONST const __code
void putchar(char c)
{
// Assume that the LCD has been init'ed with the screen cleared
// and the cursor homed the first time through
static int row = 0, col = 0;
// The dummy bytes are there to enforce a time delay
CONST uint8_t cls_cmd[] = {0xFE, 0x51, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20};
// 16 spaces to clear a line
CONST uint8_t clear_line_cmd[] = {
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20
};
CONST uint8_t bksp_cmd[] = {0xFE, 0x4E};
switch (c)
{
// Alarm bell -- we need some sort of visual bell here
case '\a':
_visual_bell();
break;
// Backspace -- this does what you think it does, although backspacing
// from column 0 needs to be handled specially
case '\b':
if (col > 0)
{
--col;
lcd_output(bksp_cmd, sizeof(bksp_cmd)/sizeof(bksp_cmd[0]));
}
else
{
if (row > 0)
{
// Move the cursor to the end of the previous line
_move_cursor(--row, col = MAXCOLS);
--col;
lcd_output(bksp_cmd, sizeof(bksp_cmd)/sizeof(bksp_cmd[0]));
}
else
{
// Backspace from 0,0 is no good -- visual bell instead
_visual_bell();
}
}
break;
// Form feed -- clears the screen and homes the cursor
case '\f':
row = 0, col = 0;
lcd_output(cls_cmd, sizeof(cls_cmd)/sizeof(cls_cmd[0]));
// Backspace out an extra space left over from adjusting the
// timing on the clear screen command
// This is a kludge -- but we don't know the exact timing on
// clearing the screen on this module, so it's the best we can do
lcd_output(bksp_cmd, sizeof(bksp_cmd)/sizeof(bksp_cmd[0]));
break;
// Newline -- we follow the UNIX convention here as moving to the
// next line without going back to col 0 is pretty useless and lame
case '\n':
row = (row + 1) % MAXROWS;
col = 0;
_move_cursor(row, col);
lcd_output(clear_line_cmd,
sizeof(clear_line_cmd)/sizeof(clear_line_cmd[0]));
_move_cursor(row, col);
break;
// Carriage return -- go back to col 0 without moving lines
case '\r':
col = 0;
_move_cursor(row, col);
break;
// VT and HT aren't supported as I have no idea what to do with them
// Someone tried to sneak a LCD command escape through STDIO --
// that's a no-no, so eat it as we can't return EOF because SDCC's
// putchar decl is silly and non-conformant
case '\xfe':
return;
// Not a character we handle, so just fling it out to the LCD
default:
if (col >= MAXCOLS) // wrap to the next line
{
row = (row + 1) % MAXROWS;
col = 0;
_move_cursor(row, col);
lcd_output(clear_line_cmd,
sizeof(clear_line_cmd)/sizeof(clear_line_cmd[0]));
_move_cursor(row, col);
}
lcd_output(&c, 1);
++col;
break;
}
return;
}
void _visual_bell(void)
{
}
void _move_cursor(int r, int c)
{
// note, this can't live in code space or be a const as we scribble
// on it to prepare the actual command
uint8_t move_cmd[] = {0xFE, 0x45, 0x00};
if (r >= MAXROWS)
r = MAXROWS;
if (c >= MAXCOLS)
c = MAXCOLS;
move_cmd[2] = ((r & 0x03) << 6) | (c & 0x3F);
lcd_output(move_cmd, sizeof(move_cmd)/sizeof(move_cmd[0]));
}
void lcd_init(void)
{
// the display defaults ON already, but force it ON because this may
// be called with the display OFF in a warm restart situation
// we also clear the screen here
CONST uint8_t cmd[] = {0xFE, 0x41, 0xFE, 0x51, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20};
lcd_output(cmd, sizeof(cmd)/sizeof(cmd[0]));
}
uint8_t rev_bits(uint8_t c);
// Write a blob of data to the LCD -- this doesn't care about control
// characters etal
void lcd_output(uint8_t* chars, int n)
{
int i = 0;
// make sure SM2 is cleared FIRST (CPOL=idle HIGH)
SM2 = 0;
// drive the slave select for the LCD
P4_6 = 0;
for (; i < n; ++i)
{
// transmit a byte -- swap the bits in it as the LCD wants data
// MSB-first, but the 8051 USART sends LSB-first only
SBUF = rev_bits(chars[i]);
// wait for it to fly out the door as we don't really have
// anything better to do
while (!TI);
TI = 0;
}
// turn off the slave select
P4_6 = 1;
}
// Reverse the bits in a byte using some clever bit fiddling
// Swaps nybbles first, then two-bit chunks, then individual adjacent bits
uint8_t rev_bits(uint8_t c)
{
c = (c & 0xF0) >> 4 | (c & 0x0F) << 4;
c = (c & 0xCC) >> 2 | (c & 0x33) << 2;
c = (c & 0xAA) >> 1 | (c & 0x55) << 1;
return c;
}
// Note that I've tried having this code set the port pin as an input-only pin for the duration of reception from the RTC, but to no avail
// Read a blob of data from the RTC chip -- takes care of the address too
void rtc_input(uint8_t addr, uint8_t* chars, int n)
{
int i = 0;
// make sure SM2 is set FIRST (CPOL = idle LOW)
SM2 = 1;
// drive the slave select for the RTC chip
P4_7 = 0;
// transmit the address byte with the MSB forced to 0
SBUF = addr & 0x7F;
// wait for it to finish
while (!TI);
TI = 0;
REN = 1;
RI = 0;
for (; i < n; ++i)
{
// wait for a byte to come in -- the RTC sends LSB-first, too
// an interrupt driven serial routine would add complexity for
// very little gain here -- the keyboard interrupt is the only
// thing we have to remain responsive to
while (!RI);
RI = 0;
chars[i] = SBUF;
}
REN = 0;
// turn off the slave select
P4_7 = 1;
}
Crédito adicional: ¿por qué hay un pulso runt al final del byte de la dirección y cómo puedo deshacerme de él?