Skip to content

Commit aea505b

Browse files
authored
Merge pull request #12 from dihm/adm_error_handling
Adm error handling
2 parents 7b04427 + 525548b commit aea505b

File tree

2 files changed

+71
-26
lines changed

2 files changed

+71
-26
lines changed

README.md

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ These commands must be run when the running status is `STOPPED`.
5959
* `get <address (in hex)>` - Gets instruction at address. Returns output word and number of clock cycles separated by a space, in same format as `set`.
6060
* `run` - Used to hardware start a programmed sequence (ie waits for external trigger before processing first instruction).
6161
* `swr` - Used to software start a programmed sequence (ie do not wait for a hardware trigger at sequence start).
62-
* `adm <number of instructions (in hex)>` - Enters mode for adding pulse instructions in binary.
63-
* The number of instructions must be specified with the command. The Pi Pico will then wait for that enough bytes to fill that many instructions (6 times the number of instructions) to be read, and will not respond during this time unless there is an error. This mode can not be be terminated until that many bytes are read.
62+
* `adm <starting instruction address (in hex)> <number of instructions (in hex)>` - Enters mode for adding pulse instructions in binary.
63+
* This command over-writes any existing instructions in memory. The starting instruction address specifies where to insert the block of instructions. This is generally set to 0 to write a complete instruction set from scratch.
64+
* The number of instructions must be specified with the command, which is used to determine the total number of bytes to be read (6 6 times the number of instructions).
65+
* This command returns `ready\r\n` to signify it is ready for binary data. The Pico will then read the total number of bytes. This mode can not be terminated until that many bytes are read.
6466
* Each instruction is specified by a 16 bit unsigned integer (little Endian, output 15 is most significant) specifying the state of the outputs and a 32 bit unsigned integer (little Endian) specifying the number of clock cycles.
6567
* The number of clock cycles sets how long this state is held before the next instruction.
6668
* If the number of clock cycles is 0, this indicates an indefinite wait.
@@ -85,27 +87,50 @@ The basis of the functionality for this serial interface was developed by Carter
8587
## Clock Sync
8688
Firmware supports the use of an external clock. This prevents any significant phase slip between a pseudoclock and this digital output controller if their clocks are phase synchronous. Without external buffering hardware, clock must be LVCMOS compatible.
8789

88-
## Example:
89-
Below program sets one output high for 1 microsecond, then low while setting the next output high for 1 microsecond (64 in hex = 100 in decimal) for 6 outputs, then stopping:
90+
## Examples:
91+
Below python script sets one output high for 1 microsecond, then low while setting the next output high for 1 microsecond (64 in hex = 100 in decimal) for 6 outputs, then stopping. `do` is a pyserial handle to the pico.
9092

91-
Commands (`\n` explicitly denotes newline/pressing enter):
93+
```python
94+
bits = [1, 2, 3, 8, 10, 20, 0, 0]
95+
cycles = [100, 100, 100, 100, 100, 100, 0, 0]
9296

93-
```
94-
add\n
95-
1 64\n
96-
2 64\n
97-
4 64\n
98-
8 64\n
99-
10 64\n
100-
20 64\n
101-
0 0\n
102-
0 0\n
103-
end\n
104-
```
97+
do.write('add\n'.encode())
98+
for bit, cycle in zip(bits, cycles):
99+
do.write(f'{bit:x} {cycle:x}\n'.encode())
105100

101+
do.write('end\n'.encode())
102+
resp = do.readline().decode()
103+
assert resp == 'ok\r\n'
104+
```
106105

106+
Output of the above sequence.
107107
<img width="470" alt="documentation" src="https://github.com/pmiller2022/prawn_digital_output/assets/75953337/932b784f-346f-4598-8679-b857578e0291">
108108

109+
This is a python script that employs the binary write `adm` command.
110+
In order to function correctly, the data to be written must be in a numpy structured array where each column dtype can be specified uniquely.
111+
Below assumes the `bits` and `cycles` are 1-D arrays that have already been created.
112+
It also assumes `do` is a pyserial handle to the device.
113+
114+
```python
115+
data = np.zeros(len(bits), dtype=[('bits', 'u2'), ('cycles','u4')])
116+
data['bits'] = bits
117+
data['cycles'] = cycles
118+
119+
serial_buffer = data.tobytes()
120+
121+
do.write(f'adm 0 {len(data):x}\r\n'.encode())
122+
resp = do.readline().decode()
123+
assert resp == 'ready\r\n', f'Not ready for binary data. Response was {repr(resp)}'
124+
do.write(serial_buffer)
125+
resp = do.readline().decode()
126+
if resp != 'ok\r\n':
127+
# if response not ok, probably more than one line, read them all
128+
# done this way to prevent readlines timeout for standard operation
129+
extra_resp = do.readlines()
130+
resp += ''.join([st.decode() for st in extra_resp])
131+
print(f'Write had errors. Response was {repr(resp)}')
132+
```
133+
109134
## Compiling the firmware
110135

111136
If you want to make changes to the firmware, or want to compile it yourself (because you don't trust binary blobs from the internet), we provide a docker configuration to help you do that.

prawn_do/prawn_do.c

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -519,17 +519,28 @@ int main(){
519519
// Add many command: read in a fixed number of binary integers without separation,
520520
// append to command array
521521
else if(strncmp(serial_buf, "adm", 3) == 0){
522-
// Get how many instructions this adm command contains
522+
// Get how many instructions this adm command contains and where to insert them
523+
uint32_t start_addr;
523524
uint32_t inst_count;
524-
int parsed = sscanf(serial_buf, "%*s %x", &inst_count);
525-
if(parsed < 1){
525+
int parsed = sscanf(serial_buf, "%*s %x %x", &start_addr, &inst_count);
526+
if(parsed < 2){
526527
fast_serial_printf("Invalid request\r\n");
528+
continue;
527529
}
528-
529530
// Check that the instructions will fit in the do_cmds array
530-
if(inst_count + do_cmd_count >= MAX_DO_CMDS - 3){
531-
fast_serial_printf("Too many DO commands (%d + %d). Please use resources more efficiently or increase MAX_DO_CMDS and recompile.\r\n", do_cmd_count, inst_count);
531+
else if(inst_count + start_addr > MAX_INSTR){
532+
fast_serial_printf("Invalid address and/or too many instructions (%d + %d).\r\n", start_addr, inst_count);
533+
continue;
532534
}
535+
else{
536+
fast_serial_printf("ready\r\n");
537+
}
538+
539+
// reset do_cmd_count to start_address
540+
do_cmd_count = start_addr * 2;
541+
542+
uint32_t reps_error_count = 0;
543+
uint32_t last_reps_error_idx = 0;
533544

534545
// It takes 6 bytes to describe an instruction: 2 bytes for values, 4 bytes for time
535546
uint32_t inst_per_buffer = SERIAL_BUFFER_SIZE / 6;
@@ -545,7 +556,9 @@ int main(){
545556
| (serial_buf[6*i+3] << 8)
546557
| serial_buf[6*i+2]);
547558
if(reps < 5 && reps != 0){
548-
fast_serial_printf("Reps must be 0 or greater than 4, got %x\r\n", reps);
559+
reps_error_count++;
560+
last_reps_error_idx = (do_cmd_count + 1) / 2;
561+
reps = 0;
549562
}
550563
if(reps != 0){
551564
reps -= 4;
@@ -569,7 +582,9 @@ int main(){
569582
| (serial_buf[6*i+3] << 8)
570583
| serial_buf[6*i+2]);
571584
if(reps < 5 && reps != 0){
572-
fast_serial_printf("Reps must be 0 or greater than 4, got %x\r\n", reps);
585+
reps_error_count++;
586+
last_reps_error_idx = (do_cmd_count + 1) / 2;
587+
reps = 0;
573588
}
574589
if(reps != 0){
575590
reps -= 4;
@@ -579,7 +594,12 @@ int main(){
579594
}
580595
}
581596

582-
fast_serial_printf("ok\r\n");
597+
if(reps_error_count > 0){
598+
fast_serial_printf("Invalid number of reps in %d instructions, most recent error at instruction %d. Setting reps to zero for these instructions.\r\n", reps_error_count, last_reps_error_idx);
599+
}
600+
else{
601+
fast_serial_printf("ok\r\n");
602+
}
583603
}
584604
// Dump command: print the currently loaded buffered outputs
585605
else if(strncmp(serial_buf, "dmp", 3) == 0){

0 commit comments

Comments
 (0)